1、保留关键字
- abstract
- after
- alias
- apply
- auto
- case
- catch
- copyof
- default
- define
- final
- immutable
- implements
- in
- inline
- let
- macro
- match
- mutable
- null
- of
- override
- partial
- promise
- reference
- relocatable
- sealed
- sizeof
- static
- supports
- switch
- try
- typedef
- typeof
- unchecked
2、实例
pragma solidity ^0.4.22;
contract SimpleStorage{
uint storedDate;
function set (uint x) public{
storedDate = x;
}
function get() public view returns(uint){
return storedDate;
}
}
pragma solidity ^0.4.22;
contract solidityTest{
constructor() public{
}
function getResult() public view returns(uint){
//这是单行注释
/*
*这是多行注释
*类似其他语言的注释
*/
uint a = 1;
uint b = 2;
uint result = a + b;
return result;
}
}
3、数据类型
3.1、值类型
类型 | 保留字 | 取值 |
---|---|---|
布尔型 | bool | true/false |
整型 | int/uint | 有符号整数/无符号整数。 |
整型 | int8 to int256 | 8位到256位的带符号整型数。int256与int相同。 |
整型 | uint8 to uint256 | 8位到256位的无符号整型。uint256和uint是一样的。 |
定长浮点型 | fixed/unfixed | 有符号和无符号的定长浮点型 |
定长浮点型 | fixedMxN | 带符号的定长浮点型,其中M表示按类型取的位数,N表示小数点。M应该能被8整除,从8到256。N可以是0到80。fixed与fixed128x18相同。 |
定长浮点型 | ufixedMxN | 无符号的定长浮点型,其中M表示按类型取的位数,N表示小数点。M应该能被8整除,从8到256。N可以是0到80。fixed与fixed128x18相同。 |
3.2、地址类型
地址类型表示以太坊地址,长度为20字节。地址可以使用
.balance
方法获得余额,也可以使用.transfer
方法将余额转到另一个地址。
address x = 0x212;
address myAddress = this;
if (x.balance < 10 && myAddress.balance >= 10)
x.transfer(10);
3.3、引用类型/复合数据类型
solidity中,有一些数据类型由值类型组合而成,相比于简单的值类型,这些类型通常通过名称引用,被称为引用类型。
引用类型包括:
- 数组 (字符串与bytes是特殊的数组,所以也是引用类型)
- struct (结构体)
- map (映射)
4、变量
4.1、状态变量
solidity 是一种静态类型语言,这意味着需要在声明期间指定变量类型。每个变量声明时,都有一个基于其类型的默认值。没有undefined
或null
的概念。
pragma solidity ^0.4.22;
contract solidityTest1{
uint storedDate; //状态变量
constructor() public {
storedDate = 10; //使用状态变量
}
}
4.2、局部变量
pragma solidity ^0.4.22;
contract solidityTest2{
uint storedDate; //状态变量
constructor() public{
storedDate = 10;
}
function getResult() public view returns(uint){
uint a=1; //局部变量
uint b=2;
uint result = a+b;
return result;//访问局部变量
}
}
4.3、全局变量
这些是全局工作区中存在的特殊变量,提供有关区块链和交易属性的信息。
名称 | 返回 |
---|---|
blockhash(uint blockNumber) returns (bytes32) | 给定区块的哈希值 – 只适用于256最近区块, 不包含当前区块。 |
block.coinbase (address payable) | 当前区块矿工的地址 |
block.difficulty (uint) | 当前区块的难度 |
block.gaslimit (uint) | 当前区块的gaslimit |
block.number (uint) | 当前区块的number |
block.timestamp (uint) | 当前区块的时间戳,为unix纪元以来的秒 |
gasleft() returns (uint256) | 剩余 gas |
msg.data (bytes calldata) | 完成 calldata |
msg.sender (address payable) | 消息发送者 (当前 caller) |
msg.sig (bytes4) | calldata的前四个字节 (function identifier) |
msg.value (uint) | 当前消息的wei值 |
Now (uint) | 当前块的时间戳 |
tx.gasprice (uint) | 交易的gas价格 |
tx.origin (address payable) | 交易的发送方 |
5、变量作用域
局部变量的作用域仅限于定义它们的函数,但是状态变量可以有三种作用域类型。
- Public – 公共状态变量可以在内部访问,也可以通过消息访问。对于公共状态变量,将生成一个自动getter函数。
- Internal – 内部状态变量只能从当前合约或其派生合约内访问。
- Private – 私有状态变量只能从当前合约内部访问,派生合约内不能访问。
实例:
pragma solidity ^0.4.22;
contract solidityTest2{
uint public data = 30;
uint internal iData= 10;
function x() public returns (uint) {
data = 3; // 内部访问
return data;
}
}
contract Caller {
solidityTest2 c = new solidityTest2();
function f() public view returns (uint) {
return c.data(); // 外部访问
}
}
contract D is solidityTest2 {
uint storedData; // 状态变量
function y() public returns (uint) {
iData = 3; // 派生合约内部访问
return iData;
}
function getResult() public view returns(uint){
uint a = 1; // 局部变量
uint b = 2;
uint result = a + b;
return storedData; // 访问状态变量
}
}
6、运算符
- 算术运算符
- 比较运算符
- 逻辑(或关系)运算符
- 赋值运算符
- 条件(或三元)运算符
6.1、算术运算符
pragma solidity ^0.4.22;
contract solidityTest3{
constructor() public{
}
function getResult() public view returns(uint){
uint a = 1;
uint b = 2;
uint result = a+b;//算数运算符
return result;
}
}
6.2、比较运算符
pragma solidity ^0.4.22;
contract solidityTest4{
uint storedDate;
constructor() public{
storedDate = 10;
}
function getResult() public view returns(string memory){
uint a = 1;//局部变量
uint b = 2;
uint result = a + b;
return integerToString(result);
}
function integerToString(uint _i) internal pure
returns(string memory _uintAsstring){
if(_i == 0){//比较运算符
return "0";
}
uint j = _i;
uint len;
while(j != 0){//比较运算符
len++;
j/=10;
}
bytes memory bstr = new bytes(len);
uint k = len - 1;
while(_i !=0){
bstr[k--] = byte(uint(48 + _i % 10));
_i /= 10;
}
return string(bstr);//访问局部变量
}
}
6.3、逻辑运算符
pragma solidity ^0.4.22;
contract solidityTest5{
uint storedDate; //状态变量
constructor() public{
storedDate = 10 ;
}
function getResult() public view returns(string memory){
uint a=1;
uint b=2;
uint result = a+b;
return integerToString(storedDate);//访问状态变量
}
function integerToString(uint _i) internal pure
returns (string memory){
if(_i ==0){
return "0";
}
uint j =_i;
uint len;
while(!(j ==0)){//逻辑运算符
len++;
j/=10;
}
bytes memory bstr = new bytes(len);
uint k = len-1;
while(_i !=0){
bstr[k--] = byte(uint8(48 + _i % 10));
_i /10;
}
return string(bstr);
}
}
7、solidity循环语句
7.1、while循环
while (表达式) {
被执行语句(如果表示为真)
}
7.2、do-while
do {
被执行语句(如果表示为真)
} while (表达式);
7.3、for
for (初始化; 测试条件; 迭代语句) {
被执行语句(如果表示为真)
}
8、solidity-数据位置
引用类型/复合数据类型
solidity中,有一些数据类型由简单数据类型组合而成,相比于简单的值类型,这些类型通常通过名称引用,被称为引用类型,
引用类型包括:
- 数组 (字符串与bytes是特殊的数组,所以也是引用类型)
- struct (结构体)
- map (映射)
这些类型涉及到的数据量较大,复制它们可能要消耗大量Gas,非常昂贵,所以使用它们时,必须考虑存储位置,例如,是保存在内存中,还是在EVM存储区中。
数据位置(data location)
在合约中声明和使用的变量都有一个数据位置,指明变量值应该存储在哪里。合约变量的数据位置将会影响Gas消耗量。
solidity 提供4种类型的数据位置。
- Storage
- Memory
- Calldata
- Stack
Storage
该存储位置存储永久数据,这意味着该数据可以被合约中的所有函数访问。可以把它视为计算机的硬盘数据,所有数据都永久存储。
保存在存储区(Storage)中的变量,以智能合约的状态存储,并且在函数调用之间保持持久性。与其他数据位置相比,存储区数据位置的成本较高。
Memory
内存位置是临时数据,比存储位置便宜。它只能在函数中访问。
通常,内存数据用于保存临时变量,以便在函数执行期间进行计算。一旦函数执行完毕,它的内容就会被丢弃。你可以把它想象成每个单独函数的内存(RAM)。
Calldata
Calldata是不可修改的非持久性数据位置,所有传递给函数的值,都存储在这里。此外,Calldata是外部函数的参数(而不是返回参数)的默认位置。
Stack
堆栈是由EVM (Ethereum虚拟机)维护的非持久性数据。EVM使用堆栈数据位置在执行期间加载变量。堆栈位置最多有1024个级别的限制。
可以看到,要永久性存储,可以保存在存储区(Storage)。
9、solidity – 变量的数据位置规则
状态变量
pragma solidity ^0.4.22;
contract DataLocation{
// 状态变量总是存储在存储区中。
//storage
uint stateVariable;
uint[] stateArray;
}
此外,不能显式地标记状态变量的位置。
pragma solidity ^0.4.22;
contract DataLocation{
// 状态变量总是存储在存储区中。
//storage
uint storage stateVariable; //错误,指明了数据存放在了储存区
uint[] memory stateArray; //错误,指明了数据存放在内存
}
函数参数与返回值
pragma solidity ^0.4.22;
contract DataLocation2{
uint stateVariable;
uint[] stateArray;
function calcuate(uint num1,uint num2) public pure returns(uint result){
return num1+num2;
}
}
此处,函数参数 uint num1
与 uint num2
,返回值 uint result
都存储在内存中。
局部变量
值类型的局部变量存储在内存中。但是,对于引用类型,需要显式地指定数据位置。
pragma solidity ^0.4.22;
contract Locations{
/* 此处都是状态变量 */
// 存储在storage中
bool flag;
uint number;
address account;
function doSomething() public{
/* 此处都是局部变量 */
// 值类型
// 所以它们被存储在内存中
bool flag2;
uint number2;
address account2;
// 引用类型,需要显示指定数据位置,此处指定为内存
uint[] memory localArry;
}
}
不能显式覆盖具有值类型的局部变量。
function doSomething() public {
/* 此处都是局部变量 */
// 值类型
bool memory flag2; // 错误
uint Storage number2; // 错误
address account2;
}
外部函数的参数
10、solidity – 赋值的数据位置规则
数据可以通过两种方式从一个变量复制到另一个变量。一种方法是复制整个数据(按值复制),另一种方法是引用复制。
存储变量赋值给存储变量
将一个状态(存储)变量赋值给另一个状态(存储)变量,将创建一个新的副本。
pragma solidity ^0.4.22;
contract Locations2{
uint public stateVar1 = 10;
uint stateVar2 = 20;
function doSomething() public returns(uint){
stateVar1 = stateVar2;
stateVar2 = 30;
return stateVar1;
}
}
stateVar1
和stateVar2
是状态变量。在doSomething
函数中,我们将stateVar2
的值复制到stateVar1
。
stateVar1
的值是20,但是为了确定它创建了一个新的副本,我们改变了stateVar2
的值。因此,如果它不创建副本,那么stateVar1
的值应该是30,创建副本则是20。
这同样适用于引用类型状态变量。
内存变量复制到存储变量
从内存变量复制到存储变量,总是会创建一个新的副本。
pragma solidity ^0.4.22;
contract Locations3{
uint stateVar= 10; //储存在storage中
function doSomething() public returns(uint){
uint localVar = 20; //在memory中
stateVar = localVar;
localVar = 40;
return stateVar; //returns 20
}
}
在上面的例子中,我们有一个状态变量和一个局部变量。在函数中,我们把局部变量的值赋给状态变量。
为了检查行为,我们改变了局部变量的值; 返回状态变量的值。这里可以看到,它会返回20,说明从内存变量复制到存储状态变量,会创建一个新的副本。
存储变量复制到内存变量
从存储变量复制到内存变量,将创建一个副本。
pragma solidity ^ 0.5.0;
contract Locations {
uint stateVar = 10; //storage
function doSomething() public returns(uint) {
uint localVar = 20; //memory
localVar = stateVar;
stateVar = 40;
return localVar; //returns 10
}
}
在这里,将状态变量的值赋给了局部变量,并改变了状态变量的值。
为了检查局部变量的值是否已经更改,返回它的值。可以看到,它会返回10,说明从状态存储变量复制到内存变量,将创建一个副本。
pragma solidity ^0.4.22;
contract Locations4{
function doSomething()
public pure returns(uint[] memory,uint[] memory){
uint[] memory LocalMemoryArray1 = new uint[](3);
LocalMemoryArray1[0]=4;
LocalMemoryArray1[1]=5;
LocalMemoryArray1[2]=6;
uint[] memory LocalMemoryArray2=LocalMemoryArray1;
LocalMemoryArray1[0]=10;
return(LocalMemoryArray1,LocalMemoryArray2);
}
}
在上面的示例中,我们在内存中初始化了一个名为localMemoryArray1的
数组变量,并赋值为4、5和6。然后,我们将该变量复制到名为localMemoryArray2
的新内存变量中。
然后,我们修改了localMemoryArray1
中第一个索引的值,并返回了两个数组。这将得到相同的结果,因为它们都指向相同的位置。
让我们对值类型进行相同的检查。下面的函数将创建一个新的副本并返回20。
11、solidity – 字符串
solidity 中,字符串值使用双引号(“)和单引号(‘)包括,字符串类型用string
表示。字符串是特殊的数组,是引用类型。
pragma solidity ^0.4.22;
contract solidityTest6{
string data = "test";
}
在上面的例子中,"test"
是一个字符串,data
是一个字符串变量。solidity提供字节与字符串之间的内置转换,可以将字符串赋给byte32
类型变量。
pragma solidity ^0.4.22;
contract solidityTest6{
bytes32 data = "test";
}
bytes到字符串的转换
可以使用string()
构造函数将bytes转换为字符串。
bytes memory bstr = new bytes(10);
string message = string(bstr);
12、solidity – 数组
数组是一种数据结构,它是存储同类元素的有序集合。
数组中的特定元素由索引访问,索引值从0开始。例如,声明一个数组变量,如numbers,可以使用numbers[0]、numbers[1]和…,数字[99]表示单个变量。
数组可以是固定大小的,也可以是动态长度的。
对于存储(storage
)数组,元素类型可以是任意的(可以是其他数组、映射或结构)。对于内存(memory
)数组,元素类型不能是映射类型,如果它是一个公共函数的参数,那么元素类型必须是ABI类型。
类型为bytes
和字符串的变量是特殊数组。bytes
类似于byte[]
,但它在calldata
中被紧密地打包。字符串等价于bytes
,但(目前)不允许长度或索引访问。
因此,相比于byte[]
,bytes
应该优先使用,因为更便宜。
声明数组
type arrayName [ arraySize ];
这是一维数组。arraySize
必须是一个大于零的整数数字,type
可以是任何数据类型。例如,声明一个uint
类型,长度为10的数组:balance
,如下所示:
uint balance[10];
初始化数组
初始化数组
uint balance[3] = [1, 2, 3];
省略长度
uint balance[] = [1, 2, 3];
将创建与前一个示例完全相同的数组。
上面的语句,将数组中第3个元素赋值为5。
访问数组元素
通过索引访问
uint salary = balance[2];
创建内存数组
可以使用new
关键字在内存中创建动态数组。与存储数组相反,不能通过设置.length
成员来调整内存动态数组的长度。
pragma solidity ^0.5.0;
contract C {
function f(uint len) {
uint[] memory a = new uint[](7);
bytes memory b = new bytes(len);
// a.length == 7, b.length == len
a[6] = 8;
}
}
length
数组有一个length
成员来表示元素数量。动态数组可以通过更改.length
成员,在存储器(而不是内存)中调整大小。创建后,内存数组的大小是固定的(但是是动态的,长度可以是函数参数)。
push
动态存储数组和bytes
(不是字符串)有一个名为push的成员函数,可用于在数组末尾追加一个元素,函数返回新的长度。
pragma solidity ^0.4.22;
contract ArrayContract1{
uint[2**20] m_aLotOfIntegers;
bool[2][] m_pairsOfFlags;
//newpair储存在内存中-这是函数参数的默认存储位置
function setAllFlagPairs(bool[2][] newPairs){
//对储存数组复制,替换传入的整个数组
m_pairsOfFlags = newPairs;
}
function setFlagPair(uint index,bool flagA,bool flagB){
//访问不存在的索引将引发异常
m_pairsOfFlags[index][0]=flagA;
m_pairsOfFlags[index][1]=flagB;
}
function changeFlagArraySize(uint newSize){
// 如果newSize更小,则删除的数组元素将被清除
m_pairsOfFlags.length = newSize;
}
function clear(){
//完全清除数组
delete m_pairsOfFlags;
delete m_aLotOfIntegers;
//效果相同
m_pairsOfFlags.length = 0;
}
bytes m_byteData;
function byteArrays(bytes data){
// byte 数组 ("bytes") 存储时没有填充(padding),
// 但是可以与“uint8[]”相同处理
m_byteData = data;
m_byteData.length +=7;
m_byteData[3]=8;
delete m_byteData[2];
}
function addFlag(bool[2] flag) returns (uint){
return m_pairsOfFlags.push(flag);
}
function createMemoryArray(uint size) returns (bytes) {
// 使用“new”创建动态内存数组:
uint[2][] memory arrayOfPairs = new uint[2][](size);
// 创建一个动态byte数组:
bytes memory b = new bytes(200);
for (uint i = 0; i < b.length; i++)
b[i] = byte(i);
return b;
}
}
13、solidity – Enum(枚举)
枚举将一个变量的取值限制为几个预定义值中的一个。精确使用枚举类型有助于减少代码中的bug。
示例
一个奶茶的应用程序,将玻璃杯的容量大小限制为:小、中、大。这将确保任何人不能购买其他容量的奶茶。
pragma solidity ^0.4.22;
contract test{
enum FreshJuiceSize{SMALL,MEDIUM,LARGE}
FreshJuiceSize choice;
FreshJuiceSize constant defaultChoice=FreshJuiceSize.MEDIUM;
function setLarge() public {
choice =FreshJuiceSize.LARGE;
}
function getChoice() public view returns(FreshJuiceSize){
return choice;
}
function getDefaultChoice() public pure returns(uint){
return uint(defaultChoice);
}
}
14、solidity – 结构体(struct)
类似于C语言,solidity 也有结构体(struct)类型,用于表示复合型数据。结构体是引用类型。
例如,一本书的信息:
- Title
- Author
- Subject
- Book ID
就可以用结构体来表示。
14.1、定义结构体
要定义结构,使用struct
关键字。struct
关键字定义了一个新的数据类型,包含多个成员。struct语句的格式如下
struct Book {
string title;
string author;
uint book_id;
}
14.2、访问结构体成员
要访问结构的任何成员,使用成员访问操作符(.
)。
示例
定义结构体,使用结构体,访问结构体中的成员。
pragma solidity ^0.4.22;
contract test2{
struct Book{ //结构体
string title;
string author;
uint book_id;
}
Book book;
function setBook() public {
book = Book('Learn Java','TP',1);
}
function getBookId() public view returns (uint){
return book.book_id;
}
}
15、solidity – 映射(mapping)类型
与数组和结构体一样,映射也是引用类型。下面是声明映射类型的语法。
mapping(_KeyType => _ValueType)
_KeyType
– 可以是任何内置类型,或者bytes和字符串。不允许使用引用类型或复杂对象。
_ValueType
– 可以是任何类型
注意
示例
pragma solidity ^0.4.22;
contract LedgerBalance{
mapping(address=>uint) public balances;
function updateBalance(uint newBalance) public{
balances[msg.sender] = newBalance;
}
}
contract Updater{
function updateBalance() public returns(uint){
LedgerBalance ledgerBalance=new LedgerBalance();
ledgerBalance.updateBalance(10);
return ledgerBalance.balances(address(this));
}
}
16、solidity – 类型转换
solidity允许类型之间进行隐式转换和显式转换。
隐式转换时必须符合一定条件,不能导致信息丢失。例如,uint8可以转换为uint16,但是int8不可以转换为uint256,因为int8可以包含uint256中不允许的负值。
16.1、显式转换
可以使用构造函数语法,显式地将数据类型转换为另一种类型。
int8 y = -3;
uint x = uint(y);
//Now x = 0xfffff..fd == 在256bit长度的格式下,-3的补码表示
转换成更小的类型,会丢失高位。
uint32 a = 0x12345678;
uint16 b = uint16(a); // b = 0x5678
转换成更大的类型,将向左侧添加填充位。
uint16 a = 0x1234;
uint32 b = uint32(a); // b = 0x00001234
转换到更小的字节类型,会丢失后面数据。
bytes2 a = 0x1234;
bytes1 b = bytes1(a); // b = 0x12
转换为更大的字节类型时,向右添加填充位。
bytes2 a = 0x1234;
bytes4 b = bytes4(a); // b = 0x12340000
只有当字节类型和int类型大小相同时,才可以进行转换。
bytes2 a = 0x1234;
uint32 b = uint16(a); // b = 0x00001234
uint32 c = uint32(bytes4(a)); // c = 0x12340000
uint8 d = uint8(uint16(a)); // d = 0x34
uint8 e = uint8(bytes1(a)); // e = 0x12
把整数赋值给整型时,不能超出范围,发生截断,否则会报错。
uint8 a = 12; // no error
uint32 b = 1234; // no error
uint16 c = 0x123456; // error, 有截断,变为 0x3456
17、solidity – 以太单位
solidity 中,以太币的单位可以使用wei
、finney
、szabo
或ether
表示。
最小的单位是wei
。1e12
表示1 x 10^12
。
assert(1 wei == 1);
assert(1 szabo == 1e12);
assert(1 finney == 1e15);
assert(1 ether == 1e18);
assert(2 ether == 2000 fenny);
时间单位
与货币单位相似,solidity中的时间单位如下:
assert(1 seconds == 1);
assert(1 minutes == 60 seconds);
assert(1 hours == 60 minutes);
assert(1 day == 24 hours);
assert(1 week == 7 days);
18、solidity – 特殊变量/全局变量
特殊变量/全局变量,是全局可用的变量,提供关于区块链的信息。下面列出了常用的特殊变量:
名称 | 返回 |
---|---|
blockhash(uint blockNumber) returns (bytes32) | 给定区块的哈希值 – 只适用于256最近区块, 不包含当前区块。 |
block.coinbase (address payable) | 当前区块矿工的地址 |
block.difficulty (uint) | 当前区块的难度 |
block.gaslimit (uint) | 当前区块的gaslimit |
block.number (uint) | 当前区块的number |
block.timestamp (uint) | 当前区块的时间戳,为unix纪元以来的秒 |
gasleft() returns (uint256) | 剩余 gas |
msg.data (bytes calldata) | 完成 calldata |
msg.sender (address payable) | 消息发送者 (当前 caller) |
msg.sig (bytes4) | calldata的前四个字节 (function identifier) |
msg.value (uint) | 当前消息的wei值 |
Now (uint) | 当前块的时间戳 |
tx.gasprice (uint) | 交易的gas价格 |
tx.origin (address payable) | 交易的发送方 |
19、solidity – 编程风格
代码布局
- 缩进 – 使用4个空格代替制表符作为缩进。避免空格与制表符混用。
- 空2行规则 – 2个合约定义之间空2行。
pragma solidity ^0.5.0;
contract LedgerBalance {
//...
}
contract Updater {
//...
}
空1行规则 – 2个函数之间空1行。在只有声明的情况下,不需要空行。
pragma solidity ^0.5.0;
contract A {
function balance() public pure;
function account() public pure;
}
contract B is A {
function balance() public pure {
// ...
}
function account() public pure {
// ...
}
}
- 行长度 – 一行不超过79个字符。
- 换行规则 – 函数声明中左括号不换行,每个参数一行并缩进,右括号换行,并对齐左括号所在行。
function_with_a_long_name(
longArgument1,
longArgument2,
longArgument3
);
variable = function_with_a_long_name(
longArgument1,
longArgument2,
longArgument3
);
event multipleArguments(
address sender,
address recipient,
uint256 publicKey,
uint256 amount,
bytes32[] options
);
MultipleArguments(
sender,
recipient,
publicKey,
amount,
options
);
pragma solidity ^0.5.0;
contract A {
constructor() public {
// ...
}
function() external {
// ...
}
// External functions
// ...
// External view functions
// ...
// External pure functions
// ...
// Public functions
// ...
// Internal functions
// ...
// Private functions
// ...
}
- 避免多余空格 – 避免在圆括号、方括号或大括号后有空格。
- 控制结构 – 大括号的左括号不换行,右括号换行,与左括号所在行对齐。
pragma solidity ^0.5.0;
contract Coin {
struct Bank {
address owner;
uint balance;
}
}
if (x < 3) {
x += 1;
} else if (x > 7) {
x -= 1;
} else {
x = 5;
}
if (x < 3)
x += 1;
else
x -= 1;
函数声明 – 使用上面的大括号规则。添加可见性标签。可见性标签应该放在自定义修饰符之前。
function kill() public onlyowner {
selfdestruct(owner);
}
映射 – 在声明映射变量时避免多余空格。
mapping(uint => uint) map; // 不是 mapping (uint => uint) map;
mapping(address => bool) registeredAddresses;
mapping(uint => mapping(bool => Data[])) public data;
mapping(uint => mapping(uint => s)) data;
变量声明 – 声明数组变量时避免多余空格。
uint[] x; // 不是 unit [] x;
字符串声明 – 使用双引号声明字符串,而不是单引号。
str = "foo";
str = "Hamlet says, 'To be or not to be...'";
代码中各部分的顺序
代码中各部分顺序如下:
- Pragma 语句
- Import 语句
- Interface
- 库
- Contract
在Interface、库或Contract中,各部分顺序应为:
命名约定
Owned.sol
pragma solidity ^0.5.0;
// Owned.sol
contract Owned {
address public owner;
constructor() public {
owner = msg.sender;
}
modifier onlyOwner {
//....
}
function transferOwnership(address newOwner) public onlyOwner {
//...
}
}
Congress.sol
pragma solidity ^0.5.0;
// Congress.sol
import "./Owned.sol";
contract Congress is Owned, TokenRecipient {
//...
}
-
结构体名称
驼峰式命名,例如: SmartCoin -
事件名称
驼峰式命名,例如:AfterTransfer -
函数名
驼峰式命名,首字母小写,比如:initiateSupply -
局部变量和状态变量
驼峰式命名,首字母小写,比如creatorAddress、supply -
常量
大写字母单词用下划线分隔,例如:MAX_BLOCKS -
修饰符的名字
驼峰式命名,首字母小写,例如:onlyAfter -
枚举的名字
驼峰式命名,例如:TokenGroup
20、solidity – 函数
函数定义
语法
function function-name(parameter-list) scope returns() {
//语句
}
函数由关键字function
声明,后面跟函数名、参数、可见性、返回值的定义。
示例
下面的例子,定义了一个名为getResult
的函数,该函数不接受任何参数:
pragma solidity ^0.4.22;
contract test1{
function getResult() public view returns(uint){
uint a=1;
uint b=2;
uint result = a + b;
return result;
}
}
函数调用与函数参数
pragma solidity ^0.5.0;
contract solidityTest {
constructor() public{
}
function getResult() public view returns(string memory){
uint a = 1;
uint b = 2;
uint result = a + b;
return integerToString(result); // 调用函数
}
function integerToString(uint _i) internal pure
returns (string memory) {
if (_i == 0) {
return "0";
}
uint j = _i;
uint len;
while (j != 0) {
len++;
j /= 10;
}
bytes memory bstr = new bytes(len);
uint k = len - 1;
while (_i != 0) {
bstr[k--] = byte(uint8(48 + _i % 10));
_i /= 10;
}
return string(bstr);// 访问局部变量
}
}
return 语句
solidity中, 函数可以返回多个值。(但是要主要版本支持)
pragma solidity ^0.5.0;
contract Test3 {
function getResult() public view returns(uint product, uint sum){
uint a = 1; // 局部变量
uint b = 2;
product = a * b; // 使用返回参数返回值
sum = a + b; // 使用返回参数返回值
// 也可以使用return返回多个值
return(a*b, a+b);
}
}
21、solidity – 函数修饰符
创建带参数修饰符和不带参数修饰符,如下所示:
contract Owner {
// 定义修饰符 onlyOwner 不带参数
modifier onlyOwner {
require(msg.sender == owner);
_;
}
// 定义修饰符 costs 带参数
modifier costs(uint price) {
if (msg.value >= price) {
_;
}
}
}
修饰符定义中出现特殊符号_
的地方,用于插入函数体。如果在调用此函数时,满足了修饰符的条件,则执行该函数,否则将抛出异常。
pragma solidity ^0.4.22;
contract Owner{
address owner;
constructor() public {
owner = msg.sender;
}
//定义修饰符 onlyOwner 不带参数
modifier onlyOwner{
require(msg.sender == owner);
_;
}
//定义修饰符costs带参数
modifier costs(uint price){
if(msg.value >= price){
_;
}
}
}
contract Register is Owner{
mapping(address => bool) registeredAddresses;
uint price;
constructor(uint initialPrice) public {price = initialPrice;}
//使用修饰符costs
function register() public payable costs(price){
registeredAddresses[msg.sender] = true;
}
//使用修饰符onlyOwer
function changePrice(uint _price) public onlyOwner{
price = _price;
}
}
22、solidity – View(视图)函数
View(视图)函数不会修改状态。如果函数中存在以下语句,则被视为修改状态,编译器将抛出警告。
Getter方法是默认的视图函数。声明视图函数,可以在函数声明里,添加view
关键字。
pragma solidity ^0.4.22;
contract Test4{
function getResult() public view returns(uint product,uint sum){
uint a = 1;
uint b = 2;
product = a*b;
sum = a+ b;
}
}
23、solidity – Pure(纯)函数
Pure(纯)函数不读取或修改状态。如果函数中存在以下语句,则被视为读取状态,编译器将抛出警告。
- 读取状态变量。
- 访问
address(this).balance
或<address>.balance
- 访问任何区块、交易、msg等特殊变量(msg.sig 与 msg.data 允许读取)。
- 调用任何不是纯函数的函数。
- 使用包含特定操作码的内联程序集。
如果发生错误,纯函数可以使用revert()
和require()
函数来还原潜在的状态更改。
示例
pragma solidity ^0.4.22;
contract Test4{
function getResult() public pure returns(uint product,uint sum){
uint a = 1;
uint b = 2;
product = a*b;
sum = a+ b;
}
}
24、solidity – fallback(回退) 函数
fallback(回退) 函数是合约中的特殊函数。它有以下特点
- 当合约中不存在的函数被调用时,将调用fallback函数。
- 被标记为外部函数。
- 它没有名字。
- 它没有参数。
- 它不能返回任何东西。
- 每个合约定义一个fallback函数。
- 如果没有被标记为
payable
,则当合约收到无数据的以太币转账时,将抛出异常。
语法
// 没有名字,没有参数,不返回,标记为external,可以标记为payable
function() external {
// statements
}
下面的示例展示了合约中的回退函数概念。
示例
pragma solidity ^0.6.2;
contract Test5{
uint public x ;
// 没有名字,没有参数,不返回,标记为external,可以标记为payable
function() external { x = 1; }
}
contract Sink {
function() external payable {}
}
contract Caller {
function callTest(Test test) public returns (bool) {
(bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));
require(success);
// test.x 是 1
address payable testPayable = address(uint160(address(test)));
// 发送以太测试合同,
// 转账将失败,也就是说,这里返回false。
return (testPayable.send(2 ether));
}
function callSink(Sink sink) public returns (bool) {
address payable sinkPayable = address(sink);
return (sinkPayable.send(2 ether));
}
}
25、solidity – 函数重载
同一个作用域内,相同函数名可以定义多个函数。这些函数的参数(参数类型或参数数量)必须不一样。仅仅是返回值不一样不被允许。
pragma solidity ^0.4.22;
contract test6 {
function getSum(uint a, uint b) public pure returns(uint){
return a + b;
}
function getSum(uint a, uint b, uint c) public pure returns(uint){
return a + b + c;
}
function callSumWithTwoArguments() public pure returns(uint){
return getSum(1,2);
}
function callSumWithThreeArguments() public pure returns(uint){
return getSum(1,2,3);
}
}
26、solidity – 数学函数
solidity 也提供了内置的数学函数。下面是常用的数学函数:
-
addmod(uint x, uint y, uint k) returns (uint)
计算(x + y) % k,计算中,以任意精度执行加法,且不限于2^256大小。 -
mulmod(uint x, uint y, uint k) returns (uint)
计算(x * y) % k,计算中,以任意精度执行乘法,且不限于2^256大小。
示例
pragma solidity ^0.4.22;
contract test7{
function callAddMod() public pure returns(uint){
// `addmod(uint x, uint y, uint k) returns (uint)` 计算(x + y) % k,计算中,以任意精度执行加法,且不限于2^256大小。
return addmod(6,5,4);
}
function callMulmod() public pure returns(uint){
// `mulmod(uint x, uint y, uint k) returns (uint)` 计算(x * y) % k,计算中,以任意精度执行乘法,且不限于2^256大小。
return mulmod(6,5,4);
}
}
27、solidity – 加密函数
solidity 提供了常用的加密函数。以下是一些重要函数:
-
keccak256(bytes memory) returns (bytes32)
计算输入的Keccak-256散列。 -
sha256(bytes memory) returns (bytes32)
计算输入的SHA-256散列。 -
ripemd160(bytes memory) returns (bytes20)
计算输入的RIPEMD-160散列。 -
ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)
从椭圆曲线签名中恢复与公钥相关的地址,或在出错时返回零。函数参数对应于签名的ECDSA值: r – 签名的前32字节; s: 签名的第二个32字节; v: 签名的最后一个字节。这个方法返回一个地址。
示例
pragma solidity ^0.5.0;
contract Test {
function callKeccak256() public pure returns(bytes32 result){
return keccak256("ABC");
}
}
28、solidity – 提款(Withdrawal)模式
当在智能合约中,直接向一个地址转账时,如该地址是一个合约地址,合约中可以编写代码,拒绝接受付款,导致交易失败。为避免这种情况,通常会使用提款模式。
提款模式是让收款方主动来提取款项,而不是直接转账给收款方。
示例
直接转账给收款方。
这是个比富游戏,智能合约接收用户发送的款项(以太),金额最高的将获得首富头衔,前一位首富失去头衔,但将获得金钱补偿,当前首富发送的款项,将转账给前首富(示例中此处使用直接转账)。
不理解游戏没关系,重点是转账给前首富时,是直接转账。
pragma solidity ^0.4.22;
contract test9 {
address payable richest;
uint public mostSent;
constructor() public payable {
richest = msg.sender;
mostSent = msg.value;
}
function becomeRichest() public payable returns (bool) {
if (msg.value > mostSent) {
// 转账给前首富,不安全方法,对方可以拒绝收款,导致交易失败,从而导致当前智能合约失败,游戏不能继续
richest.transfer(msg.value);
richest = msg.sender;
mostSent = msg.value;
return true;
} else {
return false;
}
}
}
示例
提款模式,让收款方(前首富)主动来提取款项,交易不会失败,游戏可以继续。
pragma solidity ^0.4.22;
contract test9{
address public richest;
uint public mostSent;
mapping(address => uint) pendingWithdrawals;
constructor() public payable{
richest = msg.sender;
mostSent = msg.value;
}
function becomeRichest() public payable returns (bool){
if(msg.value > mostSent){
//此处不直接转账,暂时记录应付款项
pendingWithdrawals[richest] += msg.value;
richest = msg.sender;
mostSent = msg.value;
return true;
}else{
return false;
}
}
//收款方调用这个函数,主动提取款项
function withdraw() public{
uint amount = pendingWithdrawals[msg.sender];
pendingWithdrawals[msg.sender] = 0;
msg.sender.transfer(amount);
}
}
29、solidity – 限制(restricted)访问
对合约进行访问限制,是一种常见做法。默认情况下合约是只读的,除非将合约状态指定为public。
使用限制访问修饰符,我们可以限制谁能修改合约状态,或者调用合约函数等操作。
下面示例中,创建了多个修饰符:
示例
pragma solidity ^0.4.22;
contract test10 {
// onlyBy限制可以调用该函数的调用者(根据地址)。
// onlyAfter 限制该函数只能在特定的时间段之后调用。
//costs 调用方只能在提供特定值的情况下调用此函数。。
address public owner = msg.sender;
uint public creationTime = Now;
// 修饰符定义中出现特殊符号`_`的地方,用于插入函数体。
// 如果在调用此函数时,满足了修饰符的条件,则执行该函数
// 否则将抛出异常。
modifier onlyBy(address _account) {
require(
msg.sender == _account,
"Sender not authorized."
);
_;
}
function changeOwner(address _newOwner) public onlyBy(owner) {
owner = _newOwner;
}
modifier onlyAfter(uint _time) {
require(
Now >= _time,
"Function called too early."
);
_;
}
function disown() public onlyBy(owner) onlyAfter(creationTime + 6 weeks) {
delete owner;
}
modifier costs(uint _amount) {
require(
msg.value >= _amount,
"Not enough Ether provided."
);
_;
if (msg.value > _amount)
msg.sender.transfer(msg.value - _amount);
}
function forceOwnerChange(address _newOwner) public payable costs(200 ether) {
owner = _newOwner;
if (uint(owner) & 0 == 1) return;
}
}
30、solidity – 智能合约
solidity中,合约类似于c++中的类。合约包含以下部分:
-
构造函数 – 使用
constructor
关键字声明的特殊函数,每个合约执行一次,在创建合约时调用。 - 状态变量 – 用于存储合约状态的变量。
- 函数 – 智能合约中的函数,可以修改状态变量来改变合约的状态。
可见性
与以其他语言的类一样,合约中的函数和变量也有可见性:
-
external − 外部函数由其他合约调用,要在合约内部调用外部函数,使用
this.function_name()
的方式。状态变量不能标记为外部变量。 - public − 公共函数/变量可以在外部和内部直接使用。对于公共状态变量,solidity为其自动创建一个getter函数。
- internal − 内部函数/变量只能在内部或派生合约中使用。
- private − 私有函数/变量只能在内部使用,派生合约中不能使用。
pragma solidity ^0.4.22;
contract C{
//private state variable 私有数据 不可见
uint private data;
//public state variable 公共的信息
uint public info;
//constructor 构造函数
constructor() {
info = 10;
}
//private function 私有函数
function increment(uint a) private pure returns(uint){
return a+1;
}
//public function 公共的函数
function updateDate(uint a) public {data = a;}
function getDate() public view returns(uint){return data;}
//内部函数/变量只能在内部或派生合约中使用。
function compute(uint a,uint b) internal pure returns(uint){return a+b ;}
}
//External contract
contract D{
function readDate() public returns(uint){
C c = new C();
c.updateDate(7);
// 对于公共状态变量,solidity为其自动创建一个getter函数。
return c.getDate();
}
}
//Derived contract
contract E is C{
uint private result;
C private c;
constructor() public{
c=new C();
}
function getComputedResult() public{
result = compute(3,5);
}
function getResult() public view returns(uint) {return result;}
function getDate() public view returns(uint) {return c.info();}
}
31、solidity – 合约继承
就像Java、C++中,类的继承一样,solidity中,合约继承是扩展合约功能的一种方式。solidity支持单继承和多继承。solidity中,合约继承的重要特点: