• 智能合约文档


    solidity文档审阅

    特点

    1. 基于底层账户而非utxo 所以有一个特殊的地址 定位于用户 定位于合约 合约本身是一个账户
    2. 语言内嵌框架支持支付 只需要一个关键词payable可以在语言层面支持支付 并且超级简单
    3. 存储是使用网络上的区块链 数据的每一个状态都可以永久存储 所以需要确定变量使用内存还是区块链 使用内存不是永久存储
    4. 运行环境是在p2p网络,会比较强调合约或者函数执行的方式,函数的调用由原来的简单函数变成了一个区块链上的代码执行
    5. 一旦出现异常所有的交易都能被回撤 这保证了合约执行的原子性 避免了中间状态的不一致。

    第一个例子:

    `pragma solidity ^0.4.0;
    

    contract HelloWorld{
    uint balance;
    function update(uint amount) returns (address, uint){
    balance += amount;
    return (msg.sender, balance);
    }
    }`

    读取函数输出的新值,累加到合约变量中 返回发送人的地址 和累余额。

    remix编辑器的使用

    部署函数 create 能在浏览器函数创造能调用的函数按钮。 在update输入参数,点击按钮就能执行函数并且打印结果。

    出错的话 等待浏览器加载资源完成再操作

    文件结构

    1.版本申明
    pragma solidity ^0.4.0;
    2.全局引入
    import “filename”;
    3.自定义命名空间
    import * as symbolName from “filename”
    非es6兼容的简写语法
    import “filename” as symbolName
    等同于import*as symbolName from "filename"

    注意事项:非.打头的会被认为是绝对路径 因此要引用同级目录 必须以下写法 import “./x” as x 错误方式 import “x” as x;(这样会在全局目录下 等同与网络地址)

    编译器解析文件机制

    1. 可以将一个域名下的文件映射到本地,从而从本地的某个文件中读取
      2.提供对同一实现的不同版本的支持(可能某版本的实现前后不兼容,需要区分)
      3.如果前缀相同,取最长,
      4.有一个”fallback-remapping”机制,空串会映射到“/usr/local/include/solidify”

    solc编译器

    命令行编译器 通过以下命名空间提供支持

    context:prefix=target
    

    在contex目录下的以prefix开头的会被替换为target
    举例说:如果你将github.com/ethereum/dapp-bin拷到本地的/usr/local/dapp-bin,并使用下述方式使用文件
    import “github.com/ethereum/dapp-bin/library/iterable_mapping.sol” as it_mapping;
    要编译只需要
    solc github.com/ethereum/dapp-bin=/usr/local/dapp-bin source.sol

    旧版本的写法
    solc module1:github.com/ethereum/dapp-bin=/usr/local/dapp-bin
    modeule2:github.com/ethereum/dapp-bin=/usr/local/dapp-bin_old
    source.sol

    solc仅仅允许包含实际存在的文件 他必存在于你映射后的目录里 如果你想包含绝对路径那么可以将命名空间映射为=
    注意:如果多重映射指向同一个文件 那么取最长的文件

    browser-solidity编译器

    会自动映射到github上 然后自动检索文件 因此可以通过以下方式进行引入一个迭代包
    import “github.com/ethereum/dapp-bin/library/iterable_mapping.sol” as it_mapping
    备注:未来可能会支持其他的源码方式

    代码注释:

    //注释
    多行注释/.../
    // this is a single-line comment

    文档注释:

    ///或者/../ 也可以使用Doxygen语法 可以生成对文档的说明 参数的注解或者用户在调用函数,弹出来的确认内容。

    pragma solidity ^0.4.0;
    /** @title Shape calculator.*/
    contract shapeCalculator{
    /**
    *@dev calculate a rectangle's suface and perimeter

    *@param w width of the rectangles
    
    *@param h height of the rectangles
    
    *@return s surface of the rectangles
    
    *@return p perimeter of the rectangles
    
    */
    
    function rectangles(uint w, uint h) returns (uint s, uint p){
    
        s = w * h;
    
        p = 2 * ( w + h) ;
    
    }
    

    }

    基本要素

    状态变量
    pragma solidity ^0.4.0;

    // simple store example

    contract simpleStorage{

    uint valueStore; //state variable
    

    }
    函数
    函数分为内部和外部调用 同时对于其他合约不同级别的可见性和访问控制
    函数修饰符
    事件
    事件是evm提供的一个便利接口 用于获取当前实践
    例如
    pragma solidity ^0.4.0;

    contract SimpleAuction {
    event aNewHigherBid(address bidder, uint amount);

    function  bid(uint bidValue) external {
        aNewHigherBid(msg.sender, msg.value);
    }
    

    }
    结构类型
    结构体
    pragma solidity ^0.4.0;

    contract Company {
    //user defined Employee struct type
    //group with serveral variables
    struct employee{
    string name;
    uint age;
    uint salary;
    }

    //User defined `manager` struct type
    //group with serveral variables
    struct manager{
        employee employ;
        string title;
    }
    

    }
    枚举类型
    用于列举所有的情况
    pragma solidity ^0.4.0;

    contract Home {
    enum Switch{On,Off}
    }

    库的概念

    库就是合约 他的目的在于仅仅部署一次来复用代码 通过的是DELEGATECALL这个方法 。 这意味着库函数调用的 他的代码是在上下文中执行

    使用库合约的 可以称为隐藏的父合约,他们不会显示出现在继承关系上
    对两个库文件使用的说明
    pragma solidity ^0.4.0;

    library Set {
    // We define a new struct datatype that will be used to
    // hold its data in the calling contract.
    struct Data { mapping(uint => bool) flags; }

    // Note that the first parameter is of type "storage
    // reference" and thus only its storage address and not
    // its contents is passed as part of the call. This is a
    // special feature of library functions. It is idiomatic
    // to call the first parameter 'self', if the function can
    // be seen as a method of that object.
    function insert(Data storage self, uint value)
    returns (bool)
    {
    if (self.flags[value])
    return false; // already there
    self.flags[value] = true;
    return true;
    }

    function remove(Data storage self, uint value)
    returns (bool)
    {
    if (!self.flags[value])
    return false; // not there
    self.flags[value] = false;
    return true;
    }

    function contains(Data storage self, uint value)
    returns (bool)
    {
    return self.flags[value];
    }
    }

    contract C {
    Set.Data knownValues;

    function register(uint value) {
        // The library functions can be called without a
        // specific instance of the library, since the
        // "instance" will be the current contract.
        if (!Set.insert(knownValues, value))
            throw;
    }
    // In this contract, we can also directly access knownValues.flags, if we want.
    

    }

    1.library定义了一个结构体 用来在调用合约时候使用 如果函数需要操作数据 这个数据一般是通过库函数的第一个参数传入 按惯例会把参数定为self
    2.self的类型是storage 意味着是一个函数引用 修改他会影响所有的地方
    3.库函数的使用不需要实例化 c.register 可以看出使用set.insert 但实际上当前的合约本身就是一个实例
    3.c可以直接访问 kownValues这个值可以被库函数调用

    另一种调用方法:
    `pragma solidity ^0.4.0;

    library BigInt {
    struct bigint {
    uint[] limbs;
    }

    function fromUint(uint x) internal returns (bigint r) {
        r.limbs = new uint[](1);
        r.limbs[0] = x;
    }
    
    function add(bigint _a, bigint _b) internal returns (bigint r) {
        r.limbs = new uint[](max(_a.limbs.length, _b.limbs.length));
        uint carry = 0;
        for (uint i = 0; i < r.limbs.length; ++i) {
            uint a = limb(_a, i);
            uint b = limb(_b, i);
            r.limbs[i] = a + b + carry;
            if (a + b < a || (a + b == uint(-1) && carry > 0))
                carry = 1;
            else
                carry = 0;
        }
        if (carry > 0) {
            // too bad, we have to add a limb
            uint[] memory newLimbs = new uint[](r.limbs.length + 1);
            for (i = 0; i < r.limbs.length; ++i)
                newLimbs[i] = r.limbs[i];
            newLimbs[i] = carry;
            r.limbs = newLimbs;
        }
    }
    
    function limb(bigint _a, uint _limb) internal returns (uint) {
        return _limb < _a.limbs.length ? _a.limbs[_limb] : 0;
    }
    
    function max(uint a, uint b) private returns (uint) {
        return a > b ? a : b;
    }
    

    }

    contract C {
    using BigInt for BigInt.bigint;

    function f() {
        var x = BigInt.fromUint(7);
        var y = BigInt.fromUint(uint(-1));
        var z = x.add(y);
    }
    

    }`

    编译器并不知道库的最终地址 这些地址必须linker填进的最终字节码钟 如果地址没有参数的方式正确编译器 编译后的字节码仍会包含一个这样的字符 可以手动替换为十六进制地址。

    当前库的限制

    1. 无状态变量
    2. 不能被继承
    3. 不能接收ether

    附着库

    using A for *的效果是,库A中的函数被附着在做任意的类型上。
    using A for B;指令仅在当前的作用域有效,且暂时仅仅支持当前的合约这个作用域,后续也非常有可能解除这个限制,允许作用到全局范围。如果能作用到全局范围,通过引入一些模块(module),数据类型将能通过库函数扩展功能,而不需要每个地方都得写一遍类似的代码了。

    内存变量的布局

    solidity预留了3个32字节的大小的槽位
    0-64:哈希方法的暂存空间(scratch space)
    64-96:当前已分配内存大小(也称空闲内存指针(free memory pointer))

    有一些在Solidity中的操作需要超过64字节的临时空间,这样就会超过预留的暂存空间。他们就将会分配到空闲内存指针所在的地方,但由于他们自身的特点,生命周期相对较短,且指针本身不能更新,内存也许会,也许不会被清零(zerod out)。因此,大家不应该认为空闲的内存一定已经是清零(zeroed out)的。
    空间的空间不一定会被清零。

    调用合约

    调用合约的账户 输入的数据需要符合abi的标准

    状态变量的存储模型

    特点:
    1.第一项是按照低位对其存储
    2.基本理性存储使用实际字节
    3.基本类型不能存放 放入下一个槽位
    4.结构体和数组总是占满槽位

    建议

    如果操作少于32 应当使用实际的字节
    编译器会将多个元素进行打包到一个槽位
    最后为了方便evm进行优化 尝试有意识的storage变量和结构体成员 比如应当使用uint128 uint128 uint256 而不是使用uint128 uint256 uint128
    原理:编译器会将多个元素打包一个槽位 进行一次读写 当操作momory 编译器不会优化 上述草操作没有意义

    非固定大小

    结构体和数组里的元素按照他们给定的顺序存储。
    由于他们不可预估大小。
    1.evm堆栈
    2.映射和动态数组很具规则在p沾满一个未满的槽位。 对一个的动态数组,位置p这个槽位。 对于映射,这个槽位没有填充任何数据。 数组的原始位置是keccak(256) 映射位置k.p
    pragma solidity ^0.4.4;

    contract C {
    struct s { uint a; uint b; }
    uint x;
    mapping(uint => mapping(uint => s)) data;
    }
    3.按照实际的代码来看 结构体从0开始 定义了一个结构体 但并没有对应的结构体变量 故不占用空间。 uint x实际uint256 占用32个字节 占用槽位0 所以映射data 将从槽位1开始
    data[4][9].b的位置在kecca256(uint256(9)).keccak256(uint256(4).uint256(1))+1;

    源文件映射

    作为AST输出的一部分,编译器会提供AST某个节点以应的源代码的范围。
    上述源代码的映射都使用整数来引用源代码

    特殊机制

    使用var来作为本地变量
    contract FunctionSelector {
    function select(bool useB, uint x) returns (uint z) {
    var f = a;
    if (useB) f = b;
    return f(x);
    }

    function a(uint x) returns (uint z) {
    return x * x;
    }

    function b(uint x) returns (uint z) {
    return 2 * x;
    }
    }

    可以对var赋值为不同的函数

    内部机制-清理变量

    设计思想:在任何潜在的参与数据带来副作用之前 清理掉这些脏数据。 这些脏数据会带来意想不到的错误。 特别是storage中。
    如果不会造成副作用则不会清理。

    独立的汇编语言

    1.使用他编写代码要可读
    2.从汇编语言转为字节码应该尽可能少坑
    3.控制流便于优化和验证
    因此solidity设计了下面的东西:

    提供高级组件

    for
    switch
    这样比swap dup jump jumpi 更加可读
    函数形式的语句如mul(add(x,y),7)比7y x add num更加可读

    非常规规则

    引入一个绝对阶段来实现 该阶段只能以非常规则的方式取除较高阶别的构造,并且仍允许检查低级汇编语言。 函数名变量名 这些遵循soldity的规则

    为什么使用高层级的构造器。
    每一个变量在栈的钟位置是已知的。

    示例:
    我们来考虑一下solidity程序的字节码:
    contract C {
    function f(uint x) returns (uint y) {
    y = 1;
    for (uint i = 0; i < x; i++)
    y = 2 * y;
    }
    }

    生成的汇编
    {
    mstore(0x40, 0x60) // store the "free memory pointer"
    // function dispatcher
    switch div(calldataload(0), exp(2, 226))
    case 0xb3de648b {
    let (r) = f(calldataload(4))
    let ret := $allocate(0x20)
    mstore(ret, r)
    return(ret, 0x20)
    }
    default { revert(0, 0) }
    // memory allocator
    function $allocate(size) -> pos {
    pos := mload(0x40)
    mstore(0x40, add(pos, size))
    }
    // the contract function
    function f(x) -> y {
    y := 1
    for { let i := 0 } lt(i, x) { i := add(i, 1) } {
    y := mul(2, y)
    }
    }
    }

    脱机汇编
    {
    mstore(0x40, 0x60)
    {
    let $0 := div(calldataload(0), exp(2, 226))
    jumpi($case1, eq($0, 0xb3de648b))
    jump($caseDefault)
    $case1:
    {
    // the function call - we put return label and arguments on the stack
    $ret1 calldataload(4) jump(f)
    // This is unreachable code. Opcodes are added that mirror the
    // effect of the function on the stack height: Arguments are
    // removed and return values are introduced.
    pop pop
    let r := 0
    $ret1: // the actual return point
    $ret2 0x20 jump($allocate)
    pop pop let ret := 0
    $ret2:
    mstore(ret, r)
    return(ret, 0x20)
    // although it is useless, the jump is automatically inserted,
    // since the desugaring process is a purely syntactic operation that
    // does not analyze control-flow
    jump($endswitch)
    }
    $caseDefault:
    {
    revert(0, 0)
    jump($endswitch)
    }
    $endswitch:
    }
    jump($afterFunction)
    allocate:
    {
    // we jump over the unreachable code that introduces the function arguments
    jump($start)
    let $retpos := 0 let size := 0
    $start:
    // output variables live in the same scope as the arguments and is
    // actually allocated.
    let pos := 0
    {
    pos := mload(0x40)
    mstore(0x40, add(pos, size))
    }
    // This code replaces the arguments by the return values and jumps back.
    swap1 pop swap1 jump
    // Again unreachable code that corrects stack height.
    0 0
    }
    f:
    {
    jump($start)
    let $retpos := 0 let x := 0
    $start:
    let y := 0
    {
    let i := 0
    $for_begin:
    jumpi($for_end, iszero(lt(i, x)))
    {
    y := mul(2, y)
    }
    $for_continue:
    { i := add(i, 1) }
    jump($for_begin)
    $for_end:
    } // Here, a pop instruction will be inserted for i
    swap1 pop swap1 jump
    0 0
    }
    $afterFunction:
    stop
    }

    汇编的几个阶段:
    1.解析
    2.脱汇编
    3.生成指令流
    4.生成字节码
    关键点:
    1>
    将字节流转为符号流,去掉其中的C++风格的注释(一种特殊的源代码引用的注释,这里不打算深入讨论)。
    将符号流转为下述定义的语法结构的AST。
    注册块中定义的标识符,标注从哪里开始(根据AST节点的注解),变量可以被访问。
    2>
    一个AST转换,移除其中的for,switch和函数构建。结果仍由同一个解析器,但它不确定使用什么构造。如果添加仅跳转到并且不继续的jumpdests,则添加有关堆栈内容的信息,除非没有局部变量访问到外部作用域或栈高度与上一条指令相同。
    3>
    在操作码流生成期间,我们在一个计数器中跟踪当前的栈高,所以通过名称访问栈的变量是可能的。栈高在会修改栈的操作码后或每一个标签后进行栈调整。当每一个新局部变量被引入时,它都会用当前的栈高进行注册。如果要访问一个变量(或者拷贝其值,或者对其赋值),会根据当前栈高与变量引入时的当时栈高的不同来选择合适的DUP或SWAP指令。

    solidity内联汇编

    着手解决各种特性:
    允许函数风格
    内联局部变量
    可访问外部变量
    标签
    循环
    switch语句
    函数调用
    assembly内联编译语言

    内有安全机制 指的是一种底层访问

    示例:
    使用时间:
    在编译器没办法得到有效率的代码时候非常有用。

    pragma solidity ^0.4.0;
    

    library VectorSum {
    // This function is less efficient because the optimizer currently fails to
    // remove the bounds checks in array access.
    function sumSolidity(uint[] _data) returns (uint o_sum) {
    for (uint i = 0; i < _data.length; ++i)
    o_sum += _data[i];
    }

    // We know that we only access the array in bounds, so we can avoid the check.
    // 0x20 needs to be added to an array because the first slot contains the
    // array length.
    function sumAsm(uint[] _data) returns (uint o_sum) {
        for (uint i = 0; i < _data.length; ++i) {
            assembly {
                o_sum := mload(add(add(_data, 0x20), mul(i, 0x20)))
            }
        }
    }
    

    }

    内联编译的语法

    字面量 ox123 42 或者abc
    操作码 mload sload dup1 sstore 后面有课支持的指令列表
    函数风格 add(1,mlod(0))
    标签 name:
    变量定义 let x:=7 let x:=add(y,3)
    赋值 jump(name) x3 add
    函数风格的赋值
    支持块级局部变量

    solidity惯例
    固定长度的memory数组没有一个长度字段 但他们将很快增加这个字段 让定长和变长数组之间有更好的转换能力 所以不要依赖这一点

    接口

    限制

    1. 不能继承其他合约或者接口
    2. 不能定义构造器
    3. 不能定义变量
    4. 不能定义结构体
    5. 不能定义枚举类型

    接口仅仅限制abi可以表示的内容不会有任何信息丢失

    接口用自己的关键词表示
    interface Token{
    function transfer(address recipient,uint amount);
    }
    合约可以继承接口 因为他们可以继承其他合约。

    抽象函数

    抽象函数是没有函数体的函数
    如下:
    pragma solidity ^0.4.0;

    contract Feline {
    function utterance() returns (bytes32);
    }

    这样的合约不能通过编译 即使合约内容 也包含一些正常函数

    pragma solidity ^0.4.0;

    contract Feline {
    function utterance() returns (bytes32);

    function getContractName() returns (string){
        return "Feline";
    }
    

    }

    contract Cat is Feline {
    function utterance() returns (bytes32) { return "miaow"; }
    }

    如果从一个合约从一个合约里继承 但却没有实现所有函数 那么他也是一个抽象合约

    继承

    多重继承 可以继承多个合约
    原理:父合于会被拷贝过来代码 形成一个合体

    一个特殊的例子:
    pragma solidity ^0.4.0;

    contract mortal is owned {
    function kill() {
    if (msg.sender == owner) selfdestruct(owner);
    }
    }

    contract Base1 is mortal {
    function kill() { /* do cleanup 1 */ mortal.kill(); }
    }

    contract Base2 is mortal {
    function kill() { /* do cleanup 2 */ mortal.kill(); }
    }

    contract Final is Base1, Base2 {
    }
    结果:合约只会执行base2.kill 因为会派生重写 会跳base1.kill 因为他根本不知道有base1

    一个变通的方法是使用super

    基类构造器

    pragma solidity ^0.4.0;

    contract Base {
    uint x;
    function Base(uint _x) { x = _x; }
    }

    contract Derived is Base(7) {
    function Derived(uint _y) Base(_y * _y) {
    }
    }
    一种方式是头部声名 还有一种是合约头生声名 第二钟方式优先

    多继承和线性化

    需要解决的是钻石问题 钻石问题 解决方法是参考python

    pragma solidity ^0.4.0;

    contract X {}
    contract A is X {}
    contract C is A, X {}
    错误Linearization of inheritance graph impossible
    原因是c会请求x来重写a a又是重写x 因此这是一个不可能解决的矛盾

    继承相同名字的不同类型的成员

    当继承最后导致一个合约同时才能在多个名字相同的修改器时 他会被视为一个错误 同新的如果事件与修改器重名
    或为一个例外 getter可以覆盖一个public函数

    事件

    事件是使用EVM日志内置功能的方便工具,在DAPP的接口中,它可以反过来调用Javascript的监听事件的回调。

    原理:事件可以被继承 被调用时候 出发的参数将存储到交易日志中 这些日志与合约地址关联 并合并到区块链中 区块存在就可以一直访问。

    日至不能再合约被直接访问

    如何获取日志的例子
    var abi = /* abi as generated by the compiler /;
    var ClientReceipt = web3.eth.contract(abi);
    var clientReceipt = ClientReceipt.at(0x123 /
    address */);

    var event = clientReceipt.Deposit();

    // watch for changes
    event.watch(function(error, result){
    // result will contain various information
    // including the argumets given to the Deposit
    // call.
    if (!error)
    console.log(result);
    });

    // Or pass a callback to start watching immediately
    var event = clientReceipt.Deposit(function(error, result) {
    if (!error)
    console.log(result);
    });

    步骤:
    1.使用web3调用abi 2.拿到合约地址contract.at 实例化合约 3.调用watch方法 获取result
    4.回调函数

    底层日志接口

    通过函数可以访问 logN代表当问参数的数目 默认从0开始
    第一个参数被作为日志的一部分 其他的会作为主题
    log3{
    msg.value,
    0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20,
    msg.sender,
    _id
    }

    回退函数

    没有匹配任何值会默认调用这个函数
    支付eth时也会调用这个函数。
    如果下述函数写在回退里面 那么花费将会很多
    1.写入合约
    2.创建合约
    3.执行一个外部函数
    4.发送ether

    注意 要保证你的函数花费在2300gas以内

    一个没有定义一个回退函数的合约 如果接收ether 会触发异常 返还ether(0.4.0开始) 合约要接收ether 必须事先回退函数

    常量

    可以被定义为constant
    赋值的表达式不允许:1)访问storage;2)区块链数据,如now,this.balance,block.number;3)合约执行的中间数据,如msg.gas;4)向外部合约发起调用。
    当前的支持的仅有值类型和字符串。

    常函数

    函数也可以被声明为常量 这类函数承诺自己不修改状态
    当前不会强制使用 但是建议对于不修改的数据标记为constant

    函数修改器

    修改器(Modifiers)可以用来轻易的改变一个函数的行为。比如用于在函数执行前检查某种前置条件。修改器是一种合约属性,可被继承,同时还可被派生的合约重写(override)。

    作用范围和声名

    一个变量在声明后都有初始值为字节表示的全0值。也就是所有类型的默认值是典型的零态(zero-state)。举例来说,默认的bool的值为false,uint和int的默认值为0。
    所有的类型默认零态。 举例 bool默false uint 和int默认值为0
    对于变长的数组bytes和string,默认值则为空数组和空字符串。

    另外 一个变量被声明了 那么他会在开始前被初始化位默认值
    pragma solidity ^0.4.0;

    contract C{
    function foo() returns (uint) {
    // baz is implicitly initialized as 0
    uint bar = 5;
    if (true) {
    bar += baz;
    } else {
    uint baz = 10;// never executes
    }
    return bar;// returns 5
    }
    }

    赋值

    内置结果返回多个值

    数组和自定义结构体的复杂性

    数组类型 赋值语法有一些复杂
    赋值给一个状态变量总是创建一个完全无关的拷贝
    赋值给一个局部变量 仅仅对支持的类型 如那些32字节以内的静态类型 创建一份完全无关的拷贝
    。。。。麻烦
    如果是数据结构和数组 bytes 和string 由状态变量赋值为一个局部变量,局部变量只是持有原始状态变量的一个引用。

    执行顺序

    表达式的求值顺序并不是确定的,更正式的说法是,表达式树一个节点的某个节点在求值时的顺序是不确定的,但是它肯定会比节点本身先执行。

    创建合约示例

    不能限制gas使用 因为无足够的余额 会抛出一个异常

    函数调用

    直接调用 或者递归调用

    contract C {
    function g(uint a) returns (uint ret) { return f(); }
    function f() returns (uint ret) { return g(7) + f(); }
    }

    外部函数调用

    在合约的构造器中,不能使用this调用函数,因为当前合约还没有创建完成。

    发生异常的几种情况
    如果被调用的合约不存在,或者是不包代码的帐户,或调用的合约产生了异常,或者gas不足,均会造成函数调用发生异常。

    写一个函数,只有当状态变量(state variables)的值有对应的改变时,才调用外部函数,这样你的合约就不会有可重入性漏洞。
    可重入漏洞 外部函数修改内部的值 合约之间交互存在潜在危险

    命名参数调用和匿名参数调用

    函数调用的参数,可以通过指定名字的方式调用,但可以以任意的顺序,使用方式是{}包含。但参数的类型和数量要与定义一致。

    省略函数名称

    没有使用的参数名可以省略(一般常见于返回值)。这些名字在栈(stack)上存在,但不可访问。
    没有使用的可以省略 这样的在站上存在 但是不可以访问

    控制结构

    持if,else,while,do,for,break,continue,return,?:。
    比如if(1){...}在Solidity中是无效的。因为不存在自动转换。

    入参和出参

    同javascript一样,函数有输入参数,但与之不同的是,函数可能有任意数量的返回参数。

    返回多个

    当返回多个参数时,使用return (v0, v1, ..., vn)。返回结果的数量需要与定义的一致。

    地址相关方法

    .balance (uint256):

    Address的余额,以wei为单位。

    .transfer(uint256 amount):

    发送给定数量的ether,以wei为单位,到某个地址。失败时抛出异常。

    .send(uint256 amount) returns (bool):

    发送给定数量的ether,以wei为单位,到某个地址。失败时返回false。

    .call(...) returns (bool):

    发起底层的call调用。失败时返回false。

    .callcode(...) returns (bool):

    发起底层的callcode调用,失败时返回false。

    .delegatecall(...) returns (bool):

    发起底层的delegatecall调用,失败时返回false。

    数学和加密函数

    asser(bool condition):

    如果条件不满足,抛出异常。

    addmod(uint x, uint y, uint k) returns (uint):

    计算(x + y) % k。加法支持任意的精度。但不超过(wrap around?)2**256。

    keccak256(...) returns (bytes32):

    使用以太坊的(Keccak-256)计算HASH值。紧密打包。

    sha3(...) returns (bytes32):

    等同于keccak256()。紧密打包。

    sha256(...) returns (bytes32):

    使用SHA-256计算HASH值。紧密打包。

    ripemd160(...) returns (bytes20):

    使用RIPEMD-160计算HASH值。紧密打包。

    ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address):

    通过签名信息恢复非对称加密算法公匙地址。如果出错会返回0,附录提供了一个例子1.

    revert():

    取消执行,并回撤状态变化。

    1. 在私链上 一个可能存在问题是:
      向一个不存在的合约发送消息,非常昂贵,所以才会导致Out-Of-Gas的问题。

    2.需要注意的是字面量会用,尽可能小的空间来存储它们。比如,keccak256(0) == keccak256(uint8(0)),keccak256(0x12345678) == keccak256(uint32(0x12345678))

    3.如果需要补位,需要明确的类型转换,如keccak256("x00x12")等同于keccak256(uint16(0x12))

    特殊变量和函数

    一些变量村子当前上下文中 用来获取一些区块信息

    block.blockhash(uint blockNumber) returns (bytes32),给定区块号的哈希值,只支持最近256个区块,且不包含当前区块。
    block.coinbase (address) 当前块矿工的地址。
    block.difficulty (uint)当前块的难度。
    block.gaslimit (uint)当前块的gaslimit。
    block.number (uint)当前区块的块号。
    block.timestamp (uint)当前块的时间戳。
    msg.data (bytes)完整的调用数据(calldata)。
    msg.gas (uint)当前还剩的gas。
    msg.sender (address)当前调用发起人的地址。
    msg.sig (bytes4)调用数据的前四个字节(函数标识符)。
    msg.value (uint)这个消息所附带的货币量,单位为wei。
    now (uint)当前块的时间戳,等同于block.timestamp
    tx.gasprice (uint) 交易的gas价格。
    tx.origin (address)交易的发送者(完整的调用链)

    时间单位

    1 == 1 seconds
    1 minutes == 60 seconds
    1 hours == 60 minutes
    1 days == 24 hours
    1 weeks = 7 days
    1 years = 365 days

    如果需要进行单位转换 要特别小心 不是每天都有24小时 不是每年都是365天
    解决方案: 必须有一个外部的orcale来跟新得到一个准确的日历(内部消耗gas)

    示范:
    pragma solidity ^0.4.0;

    contract DeleteExample{

    function nowInSeconds() returns (uint256){
        return now;
    }
    
    function f(uint start, uint daysAfter) {
        if (now >= start + daysAfter * 1 days) { 
            
        }
    }
    

    }

    货币单位

    一个字面量的数字,可以使用后缀wei,finney,szabo或ether来在不同面额中转换。不含任何后缀的默认单位是wei。如2 ether == 2000 finney的结果是true。

    类型推断

    函数的参数,包括返回参数,不可以使用var这种不指定类型的方式。
    代码for (var i = 0; i < 2000; i++) {}将是一个无限循环,因为一个uint8的i的将小于2000。
    代码不能编译通过

    编译器会根据第一个值进行推断

    基本类型间的转换

    隐士转换和显示转换。
    Uint8 可以转换成uint256 但是int8不能转换成uint256
    因为uint256不能表示-1

    显示转换:
    pragma solidity ^0.4.0;

    contract DeleteExample{
    uint a;

    function f() returns (uint){
      int8 y = -3;
      uint x = uint(y);
      return x;
    }
    

    }

    uint32 a = 0x12345678;
    uint16 b = uint16(a); // b will be 0x5678 now

    左值的相关运算符

    运算有
    -= += *= %= |= &= ^= ++ --

    不能对一个storage的类型赋值

    映射 字典

    pragma solidity ^0.4.0;

    //file indeed for compile
    //may store in somewhere and import
    contract MappingExample{
    mapping(address => uint) public balances;

    function update(uint amount) returns (address addr){
        balances[msg.sender] = amount;
        return msg.sender;
    }
    

    }

    contract MappingUser{

    address conAddr;
    address userAddr;
    
    function f() returns (uint amount){
    //address not resolved!
    //tringing
        conAddr = hex"0xf2bd5de8b57ebfc45dcee97524a7a08fccc80aef";
        userAddr = hex"0xca35b7d915458ef540ade6068dfe2f44e8fa733c";
        
        return MappingExample(conAddr).balances(userAddr);
    }
    

    }

    结构体

    solidity 和结构体自定义类型
    不能声明一个struct同时将这个struct作为这个struct的一个成员。这个限制是基于结构体的大小必须是有限的。

    虽然数据结构能作为一个mapping的值,但数据类型不能包含它自身类型的成员,因为数据结构的大小必须是有限的。

    需要注意的是在函数中,将一个struct赋值给一个局部变量(默认是storage类型),实际是拷贝的引用,所以修改局部变量值时,会影响到原变量。

    当然,你也可以直接通过访问成员修改值,而不用一定赋值给一个局部变量,如campaigns[comapingnId].amount = 0

    数组

    storage来说 元素的类型是任意的 类型可以是数组映射或者数据结构

    可以使用new新建momory数组 但是不能更改 storag可以修改。 Error: Expression has to be an lvalue.

    内联数组

    数组字面量,是指以表达式方式隐式声明一个数组,并作为一个数组变量使用的方式。下面是一个简单的例子:
    pragma solidity ^0.4.0;

    contract C {
    function f() {
    g([uint(1), 2, 3]);
    }
    function g(uint[3] _data) {
    // ...
    }
    }

    abi不能支持数组 以后也许会解除限制

    数组的方法 和属性

    length属性
    不能访问超出数组长度的方式

    push方法
    storage的变长数组都有一个push

    限制的情况处理:
    web3.js能返回数据 但是不能返回数据 一种方法是使用一个非常大的静态数组

    数据位置

    calldata
    存储的是函数 并且是只读的 不会被永久存储 外部函数的参数被强制指定为calldata 效果与momoey差不多

    强制的数据位置(Forced data location)

    外部函数(External function)的参数(不包括返回参数)强制为:calldata
    状态变量(State variables)强制为: storage

    默认数据位置(Default data location)

    函数参数括返回参数:memory
    所有其它的局部变量:storage

    引用类型

    复杂类型
    不同于之前的类型 复杂类型占据更大的空间 超256 拷贝占据空间更多的空间

    函数

    1.赋值
    2.参数传递
    3.函数调用中返回

    外部函数
    内部函数
    函数的完整定义
    function () {internal(默认)|external} [constant] [payable] [returns ()]
    默认是内部调用external 没有返回必须值省略结果returns

    f和this.f的区别
    前者是内部调用 后者是外部调用
    这里的this和大多数语言向违背

    library知识总结

    library是什么呢。
    library引入时为什么使用using,这和文件引入的import有何区别。
    library内的函数全是internal的。
    library内的函数,他的参数函数为什么是internal的,不应该是external的?
    uint[]是什么类型,不能写做[]uint
    memory又是什么呢,为什么map函数明明是两个参数,但只需要传一个呢。

    枚举

    用户自定义类型 应当至少一名成员

    十六进制字面量

    由于一个字节是8位,所以一个hex是由两个[0-9a-z]字符组成的。所以var b = hex"A";不是成双的字符串是会报错的。

    转换

    pragma solidity ^0.4.0;

    contract HexLiteralBytes{
    function test() returns (bytes4, bytes1, bytes1, bytes1, bytes1){
    bytes4 a = hex"001122FF";

      return (a, a[0], a[1], a[2], a[3]);
    

    }
    }

    Result: "0x001122ff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000ff00000000000000000000000000000000000000000000000000000000000000"
    Transaction cost: 21857 gas.
    Execution cost: 585 gas.
    Decoded:
    bytes4: 0x001122ff
    bytes1: 0x00
    bytes1: 0x11
    bytes1: 0x22
    bytes1: 0xff

    字符串

    字符串字面量是指由单引号,或双引号引起来的字符串。字符串并不像C语言,包含结束符,foo这个字符串大小仅为三个字节。

    定长字节数组

    字符串的长度也是可以边唱的 可以转化为字节

    pragma solidity ^0.4.0;
    

    contract StringConvert{
    function test() returns (bytes3){
    bytes3 a = "123";

      //bytes3 b = "1234";
      //Error: Type literal_string "1234" is not implicitly convertible to expected type bytes3.
    
      return a;
    

    }
    }

    转义字

    字符串字面量支持转义字符,比如 ,xNN,uNNNN。其中xNN表式16进制值,最终录入合适的字节。而uNNNN表示Unicode码点值,最终会转换为UTF8的序列。

    小说

    定点数
    小数字面量
    支持的运算符
    数字字面量
    大多数含小数的十进制,均不可被二进制准确表达,比如5.3743的类型可能是ufixed8*248。如果你想使用这样的值,需要明确指定精度x + ufixed(5.3743),否则会报类型转换错误。
    字面量截断
    字面量转换
    pragma solidity ^0.4.0;

    contract IntegerLiteralConvert{
    function literalTest(){
    uint128 a = 1;
    //uint128 b = 2.5 + a + 0.5;
    //Error: Operator + not compatible with types rational_const 5/2 and uint128
    }
    }

    字节数组

    定长字节数组
    bytes1, ... ,bytes32,允许值以步长1递增。byte默认表示byte1。
    运算符
    <=,<,==,!=,>=,>,返回值为bool类型。
    &,|,^(异或),~非
    成员变量
    .length表示这个字节数组的长度(只读)。
    动态大小的字节数组
    bytes: 动态长度的字节数组,参见数组(Arrays)。非值类型1。

    string: 动态长度的UTF-8编码的字符类型,参见数组(Arrays)。非值类型[valueType]。

    习惯说: bytes用来存储任意长度字节数据 如果长度确认 尽量使用byte1到byte32之间的一个

    地址

    以太坊地址的长度,大小20个字节,160位,所以可以用一个uint160编码。
    地址是所有合约的基础。 所有的合约都会继承地址对象

    运算符

    地址类型成员
    属性:balance
    函数:send(),call(),delegatecall(),callcode()。
    字面量

    39-41位长的没有通过合法检查 会提出警告
    balance

    核心点是添加一个双引号

    this
    返回当前合约

    send
    像某个地址发钱 单位是WEI

    充值合约
    pragma solidity ^0.4.0;

    //请注意这个仅是Demo,请不要用到正式环境
    contract PayTest {
    //得到当前合约的余额
    function getBalance() returns (uint) {
    return this.balance;//0
    }

    //向当前合约存款
    function deposit() payable returns(address addr, uint amount, bool success){
        //msg.sender 全局变量,调用合约的发起方
        //msg.value 全局变量,调用合约的发起方转发的货币量,以wei为单位。
        //send() 执行的结果
        return (msg.sender, msg.value, this.send(msg.value));
    }
    

    }

    这个合约实现的是充值。this.send(msg.value)意指向合约自身发送msg.value量的以太币。msg.value是合约调用方附带的以太币。

    深度不超过1024 如果gas不够执行失败 使用这个方法检查成功与否 执行失败 所有交易回撤
    call(),delegatecall(),callcode()都是底层的消息传递调用,最好仅在万不得已才进行使用,因为他们破坏了Solidity的类型安全。
    他们的区别在于call可以调用支持的abi协议 call方法返回一个bool值 delegatecall仅仅执行另一个工具箱 callcode访问权限收到限制

    布尔

    !逻辑非

    && 逻辑与

    || 逻辑或

    == 等于

    != 不等于

    值引用和引用类型

    Solidity是一个静态类型的语言,所以编译时需明确指定变量的类型. solidity提供了一些基本类型来组合复杂类型

    值类型(Value Type)
    值类型包含

    布尔(Booleans)
    整型(Integer)
    地址(Address)
    定长字节数组(fixed byte arrays)
    有理数和整型(Rational and Integer Literals,String literals)
    枚举类型(Enums)
    函数(Function Types)

    不定长字节数组(bytes)
    字符串(string)
    数组(Array)
    结构体(Struts)

    智能合约源文件概述

    合约类似面向对象语言中的类。
    支持继承

    状态变量(State Variables),函数(Functions),函数修饰符(Function Modifiers),事件(Events),结构类型(Structs Types)和枚举类型(Enum Types)。

  • 相关阅读:
    机械键盘与选购技巧
    计算机科学与技术专业的知识架构
    加法器
    计算机底层逻辑电路
    小数在计算机的表示
    整数在计算机里的表示
    Core ML 机器学习
    MapFileParser.sh: Permission denied
    iOS开发创建UI的耗时操作处理
    iOS application/json上传文件等
  • 原文地址:https://www.cnblogs.com/xiaocongcong888/p/9599986.html
Copyright © 2020-2023  润新知