通读solidity官方文档


智能合约
  • Solidity是一种面向合同编写的高级语言,用于实现智能合同。它受到c++PythonJavaScript的影响,被设计为在Ethereum虚拟机(EVM)上运行。
  • Solidity是静态类型的,支持继承、库和复杂的用户定义类型。
  • 正如您将看到的,可以为投票、众筹、盲目拍卖、数字钱包等创建智能合约。

一、合约的结构

合约在Solidity中,和面向对象语言中的Class类似。每个合约中可以包含状态变量,函数,函数修饰符,事件,结构类型以及枚举类型。此外合约可以继承另外一个合约。

  • 状态变量 State variable
  • 函数 Function
  • 函数修饰符 Modifier
  • 事件 Event
  • 结构类型 Struct Types
  • 枚举类型 enum Types

状态变量 State variable

状态变量是永久存储在合约中的值。

pragma solidity ^0.4.0;

contract SimpleStorage {
    uint storedData; // State variable
    // ...
}

函数 Function

函数是合约中可执行的代码单元。

pragma solidity ^0.4.0;

contract SimpleAuction {
    function bid() public payable { // 函数 Function
        // ...
    }
}

函数修饰符 Modifier

函数修饰符可用于以声明的方式修改函数的语义(请参阅合约部分中的函数修饰符)。

pragma solidity ^0.4.22;

contract Purchase {
    address public seller;

    modifier onlySeller() { // 修饰符 Modifier
        require(
            msg.sender == seller,
            "Only seller can call this."
        );
        _;
    }

    function abort() public onlySeller { // 使用修饰符
        // ...
    }
}

事件 Event

事件是在EVM日志功能中方便使用的接口。

pragma solidity ^0.4.21;

contract SimpleAuction {
    event HighestBidIncreased(address bidder, uint amount); // Event 事件

    function bid() public payable {
        // ...
        emit HighestBidIncreased(msg.sender, msg.value); // Triggering event 触发事件
    }
}

结构类型 Struct Types

struct 中可以自定义组合多个变量类型(请参阅类型部分的Struts)

pragma solidity ^0.4.0;

contract Ballot {
    struct Voter { // 结构体 Struct 
        uint weight;
        bool voted;
        address delegate;
        uint vote;
    }
}

枚举类型 Enum Types

枚举可以使用有限的“常量值”集创建自定义类型(请参阅类型部分的Enum)。

pragma solidity ^0.4.0;

contract Purchase {
    enum State { Created, Locked, Inactive } // Enum
}

二、类型 Types

值类型

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

引用类型

  • 数据位置(Data location)

每个复杂类型,例如数组和结构,都有一个附加的注解,即“数据位置”,关于它是存储在memory还是存储在storage。根据上下文的不同,总是存在一个默认值,但是可以通过类型判断。函数参数(包括返回参数)的默认值是存储在memory,本地变量的默认值是存储在storage

还有第三个数据位置calldata,它是一个不可修改的、非持久性的区域,函数参数在其中存储。外部函数的函数参数(而不是返回参数)被强制使用calldata,用法更像 memory

pragma solidity ^0.4.0;

contract C {
    uint[] x; // x 的数据位置是指向storage

    // memoryArray 的数据位置是内存,
    function f(uint[] memoryArray) public {
        x = memoryArray; // 把数组中所有数据放在storage中
        var y = x; // 分配一个指针,y的数据位置是storage
        y[7]; // 返回第8个元素
        y.length = 2; // 通过y的指向,修改x的值
        delete x; // 清空x的数组,同时也修改了y
        // 接下来这些代码不会生效,它需要创建一个副本 /
        // 重命名storage中的数组,但是storage是静态分配的。
        // y = memoryArray;
        // 这也不起作用,因为它将“重置”指针,但是它不能指向任何合理的位置。
        // delete y;
        g(x); // 调用g,传递一个对x的引用值
        h(x); // 调用h并在内存中创建一个独立的临时副本
    }

    function g(uint[] storage storageArray) internal {}
    function h(uint[] memoryArray) public {}
}
  • 总结:
    强制指定数据存放位置:
    -> 外部函数的参数(不是返回值):calldata
    -> 状态变量:storage
    默认数据存放的位置:
    -> 函数参数(含返回值):memory
    -> 所有其他本地的变量:storage
  • 数组 Arrays

数组在编译的时候可以是固定大小,也可以是动态的。对于storage 中的数组,元素类型可以是任何类型(例如:其他arraysmappingstructs)。对于memory数组,它不能是mapping 。并且如果是一个公开可见函数的参数,必须是ABI类型。
举例:定义长度为5的uint动态数组是uint[][5](注意,与其他一些语言相比,符号是颠倒的),要访问第三个动态数组中的第二个uint,可以使用x[2][1](索引是基于零的,访问工作与声明的方式相反,即x[2]从右边的类型中删除一个级别)。

字节和字符串类型的变量是特殊的数组。字节类似于byte[],但它在calldata中被紧密地打包。字符串等于bytes,但不允许长度或索引访问(目前为止)。
注意:
所以bytesbyte[]更可取,因为它开销更小。
由于bytesstring,可以自由转换,你可以将字符串s通过bytes(s)转为一个bytes。但需要注意的是通过这种方式访问到的是UTF-8编码的码流,并不是独立的一个个字符。比如中文编码是多字节,变长的,所以你访问到的很有可能只是其中的一个代码点。

  • 结构体 Structs
    Solidity 提供一种新的方式定义结构体,如下面代码:
pragma solidity ^0.4.11;

contract CrowdFunding {
    // 定义一种新的类型有两个属性字段
    struct Funder {
        address addr;
        uint amount;
    }

    struct Campaign {
        address beneficiary;
        uint fundingGoal;
        uint numFunders;
        uint amount;
        mapping (uint => Funder) funders;
    }

    uint numCampaigns;
    mapping (uint => Campaign) campaigns;

    function newCampaign(address beneficiary, uint goal) public returns (uint campaignID) {
        campaignID = numCampaigns++; // campaignID 返回一个变量
        // 创建新的结构体并保存在 storage 中。这里我们不用赋值 mapping 类型
        campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);
    }

    function contribute(uint campaignID) public payable {
        Campaign storage c = campaigns[campaignID];
        // 创建一个新的临时memory结构,初始化给定值。
        // 并将其复制到 storage 中。
        // 注意,您还可以使用Funder(msg.sender, msg.value) 来初始化。
        c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value});
        c.amount += msg.value;
    }

    function checkGoalReached(uint campaignID) public returns (bool reached) {
        Campaign storage c = campaigns[campaignID];
        if (c.amount < c.fundingGoal)
            return false;
        uint amount = c.amount;
        c.amount = 0;
        c.beneficiary.transfer(amount);
        return true;
    }
}

上面的代码向我们展示的一个简化版的众筹项目,其实包含了一些 struct的使用。
结构体可以包含 mappingarrays ,但是不能包含 自己类型的结构体,因为结构体的大小是有限的。

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

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

映射 Mappings

Mapping 类型被声明为Mapping(_KeyType => _ValueType)。这里的_KeyType几乎可以是任何类型,除了Mapping、动态大小的数组 Arrays、合约 contract、枚举 enum 和结构 struct_ValueType实际上可以是任何类型,包括Mapping

Mapping可以被视为hash表,它实际上是初始化的,以便每个可能的键都存在,并映射到一个值,该值的字节表示都是0:类型的默认值。但是,相似之处就在这里结束了:key数据实际上并没有存储在Mapping中,只有它的keccak256 hash值用于查找值。

因此,Mapping没有长度或键或值“set”的概念。
映射只允许状态变量(或作为内部函数中的存储引用类型)。

可以将Mapping标记为public,并合约创建一个getter_KeyType将成为getter的必需参数,它将返回_ValueType

_ValueType也可以是Mapping。getter将递归地为每个_KeyType提供一个参数。

pragma solidity ^0.4.0;

contract MappingExample {
    mapping(address => uint) public balances;

    function update(uint newBalance) public {
        balances[msg.sender] = newBalance;
    }
}

contract MappingUser {
    function f() public returns (uint) {
        MappingExample m = new MappingExample();
        m.update(100);
        return m.balances(this);
    }
}

注意:Mapping 是不可迭代的,但是可以在其之上实现数据结构。例如,请参见可迭代映射

左值的相关运算符 Operators Involving LValues

左值,是指位于表达式左边的变量,可以是与操作符直接结合的形成的,如自增,自减;也可以是赋值,位运算。
可以支持操作符有:-=,+=,*=,%=,|=,&=,^=,++,--

特殊的运算符delete

delete运算符,用于将某个变量重置为初始值。对于整数,运算符的效果等同于a = 0。而对于定长数组,则是把数组中的每个元素置为初始值,变长数组则是将长度置为0。对于结构体,也是类似,是将所有的成员均重置为初始值。

delete对于mapping几乎无影响,因为键可能是任意的,且往往不可知。所以如果你删除一个结构体,它会递归删除所有非mapping的成员。当然,你是可以单独删除 mapping里的某个键,以及这个 mapping的某个值。

需要强调的是delete a的行为更像赋值,为a赋予一个新对象。我们来看看下文的示例:

pragma solidity ^0.4.0;

contract DeleteExample {
    uint data;
    uint[] dataArray;

    function f() public {
        uint x = data;
        delete x; // 把x设置为0,但是不影响x的结构
        delete data; // sets data to 0, does not affect x which still holds a copy
        uint[] storage y = dataArray;
        delete dataArray; //删除data,同样也不会影响x,因为是值传递,它存的是一份原值的拷贝。
        // y is affected which is an alias to the storage object
        // On the other hand: "delete y" is not valid, as assignments to local variables
        // referencing storage objects can only be made from existing storage objects.
    }
}

基本类型间的转换

  • 隐式转换
    如果一个操作符被应用到不同的类型,编译器会试图隐式地将一个操作数转换为另一个操作数(同样适用于赋值)。一般来说,如果值类型之间的隐式转换在语义上有意义并且没有信息丢失,那么它是可能的:uint8可以转换为uint16,而int128可以转换为int256,但是int8不能转换为uint256(因为uint256不能保存例如-1)。此外,无符号整数可以转换为大小相同或更大的字节,但反之亦然。可以转换为uint160的任何类型也可以转换为address
  • 显式转换
    如果编译器不允许隐式转换,但是您知道自己在做什么,那么显式类型转换有时是可能的。请注意,这可能会给您一些意料之外的行为,所以一定要测试以确保结果是您想要的!在下面的例子中,你将一个负的int8转换成一个uint:
int8 y = -3;
uint x = uint(y);

在这个代码段的末尾,x将有值0xfffff..fd(64个十六进制字符),是-3在两个二进制的256位的补码表示法中。
如果一个类型被显式地转换为较小的类型,高阶位将被切断:

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

-类型推断(Type Deduction)
为了方便,并不总是需要明确指定一个变量的类型,编译器会通过第一个向这个对象赋予的值的类型来进行推断

uint24 x = 0x123;
var y = x;

函数的参数,包括返回参数,不可以使用var这种不指定类型的方式。

需要特别注意的是,由于类型推断是根据第一个变量进行的赋值。
所以代码for (var i = 0; i < 2000; i++) {}将是一个无限循环,因为一个uint8的i的将小于2000

pragma solidity ^0.4.4;

contract Test{
    function a() returns (uint){
      uint count = 0;
        for (var i = 0; i < 2000; i++) {
            count++;
            if(count >= 2100){
                break;
            }
        }
        return count;
    }
}

单位和全局可用的变量Units and Globally Available Variables

  • 以太坊单位 Ether Units
    一个数字可以在以太坊上以wei、finney、szabo或 ether 后缀的单位进行转换,其中假定没有后缀的以太货币数为 wei ,例如,2 ether = 2000 finney的值为true。
  • 时间单位 Time Units
    在以秒为基本单位的时间单位和以秒为基本单位的单位之间,可以用诸如秒、分、小时、日、周、年等后缀来表示时间单位之间的转换。

1 = = 1秒
1分钟= 60秒
1小时= 60分钟
1天= 24小时
1周= 7天
1年= 365天
如果你用这些单位进行日历计算,要小心,因为不是每一年都等于365天,甚至不是每一天都因为闰秒而有24小时。由于无法预测闰秒,因此必须由外部oracle更新确切的日历库。

特殊的变量和函数 Special Variables and Functions

全局命名空间中总是存在一些特殊的变量和函数,它们主要用于提供关于区块链的信息,或者是通用的实用工具函数。

  • 块和交易属性 Block and Transaction Properties
    block.blockhash(uint blockNumber) returns (bytes32): hash of the given block - only works for 256 most recent, excluding current, blocks - deprecated in version 0.4.22 and replaced by blockhash(uint blockNumber).
    block.coinbase (address): current block miner’s address
    block.difficulty (uint): current block difficulty
    block.gaslimit (uint): current block gaslimit
    block.number (uint): current block number
    block.timestamp (uint): current block timestamp as seconds since unix epoch
    gasleft() returns (uint256): remaining gas
    msg.data (bytes): complete calldata
    msg.gas (uint): remaining gas - deprecated in version 0.4.21 and to be replaced by gasleft()
    msg.sender (address): sender of the message (current call)
    msg.sig (bytes4): first four bytes of the calldata (i.e. function identifier)
    msg.value (uint): number of wei sent with the message
    now (uint): current block timestamp (alias for block.timestamp)
    tx.gasprice (uint): gas price of the transaction
    tx.origin (address): sender of the transaction (full call chain)
请注意
msg 的所成员变量,包括msg.sender和msg.value可以随着每次外部函数调用而改变。这包括对库函数的调用。
请注意
不要依赖 block.timestamp,now 和 blockhash 作为机性的来源,除非你知道自己在做什么。

时间戳和块散列在某种程度上都可以受到挖掘程序的影响。例如,采矿社区中的不良行为者可以在选定的散列上运行赌场支付函数,如果他们没有收到任何钱,就重新尝试不同的散列。

当前块时间戳必须严格大于上一个块的时间戳,但唯一的保证是它将介于标准链中两个连续块的时间戳之间。
请注意
由于可伸缩性的原因,块散列不能用于所有块。您只能访问最近的256块的散列,所有其他值都为零。

ABI 转码的函数 ABI Encoding Functions

  • abi.encode(...) returns (bytes): ABI-encodes the given arguments
  • abi.encodePacked(...) returns (bytes): Performes packed encoding of the given arguments
  • abi.encodeWithSelector(bytes4 selector, ...) returns (bytes): ABI-encodes the given arguments
    starting from the second and prepends the given four-byte selector
  • abi.encodeWithSignature(string signature, ...) returns (bytes): Equivalent to
  • abi.encodeWithSelector(bytes4(keccak256(signature), ...)

异常处理 Error Handling

assert(bool condition):
如果条件不满足,则使交易无效——用于内部错误。
require(bool condition):
如果条件不满足,则返回—用于输入或外部组件中的错误。
require(bool condition, string message):
如果条件不满足,则返回—用于输入或外部组件中的错误。还提供一个错误消息。
revert():
中止执行并恢复状态更改
revert(string reason):
中止执行并恢复状态更改,提供一个解释字符串

数学和加密功能 Mathematical and Cryptographic Functions

以下函数可以根据方法和返回值的字面意思理解,因为这里翻译很怪 ,或者参考资料

addmod(uint x, uint y, uint k) returns (uint):
mulmod(uint x, uint y, uint k) returns (uint):
keccak256(...) returns (bytes32):
sha256(...) returns (bytes32):
sha3(...) returns (bytes32):
ripemd160(...) returns (bytes20):
ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address):
keccak256("ab", "c")
keccak256("abc")
keccak256(0x616263)
keccak256(6382179)
keccak256(97, 98, 99)

账户相关 Address Related

<address>.balance (uint256): 返回Wei单位的余额
<address>.transfer(uint256 amount): 给该Address发送 Wei单位的amount,默认消耗2300gas ,不可调整
<address>.send(uint256 amount) returns (bool):
给该Address发送 Wei单位的amount, 失败的时候返回false,默认消耗2300gas ,不可调整
<address>.call(...) returns (bool): 失败的时候返回false,转发所有gas,可调整
<address>.callcode(...) returns (bool):失败的时候返回false,转发所有gas,可调整
<address>.delegatecall(...) returns (bool):失败的时候返回false,转发所有gas,可调整

合同相关 Contract Related

this (current contract’s type):
指当前合同
selfdestruct(address recipient)
销毁当前合同,将其资金发送到指定地址
suicide(address recipient)
已弃用的别名 ,请使用selfdestruct

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 序言本文是 Solidity 文档(以太坊官方 Solidity 开发手册)中文版连载的第五部分。这个连载的前四部...
    风静縠纹平阅读 1,261评论 0 1
  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 13,727评论 0 38
  • 1. 合约A中引用合约B,是根据 import和路径 引用。一旦合约A编译完成,那么意味着合约B此时此刻的abi...
    _Danniel_阅读 4,688评论 0 11
  • 仿佛那是一个梦 你对我说 I Miss You 在梦里 我们不再分离 在梦里 你在书房工作 我在衣帽间里整理衣物 ...
    MSpink阅读 273评论 0 0
  • 乔任梁不幸离世以后,关于“抑郁症”的种种占据了社交圈的80%,作为一名曾经的中度抑郁症患者,我觉得很多都是蹭热度,...
    临渊_lx阅读 1,939评论 15 12