智能合约二————智能合约进阶

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 支持三种类型的变量:

  • 状态变量 – 变量值永久保存在合约存储空间中的变量。
  • 局部变量 – 变量值仅在函数执行过程中有效的变量,函数退出后,变量无效。
  • 全局变量 – 保存在全局命名空间,用于获取区块链相关信息的特殊变量。

Solidity 是一种静态类型语言,这意味着需要在声明期间指定变量类型。每个变量声明时,都有一个基于其类型的默认值。没有undefinednull的概念。

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、运算符

Solidity 支持以下类型的运算符:

  • 算术运算符
  • 比较运算符
  • 逻辑(或关系)运算符
  • 赋值运算符
  • 条件(或三元)运算符

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 num1uint 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;  

  } 

外部函数的参数

外部函数的参数(不包括返回参数)存储在Calldata中。

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;
    }
}

stateVar1stateVar2是状态变量。在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 – 可以是任何类型

注意

  • 映射的数据位置(data location)只能是storage,通常用于状态变量。
  • 映射可以标记为public,Solidity 自动为它创建getter。

示例

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 中,以太币的单位可以使用weifinneyszaboether表示。

最小的单位是wei1e12表示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
);
  • 源码编码 – UTF-8
  • Import – Import语句应该放在文件的顶部,pragma声明之后。
  • 函数顺序 – 函数应该根据它们的可见性来分组。
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中,各部分顺序应为:

  • Type declaration / 类型声明
  • State variable / 状态变量
  • Event / 事件
  • Function / 函数

命名约定

  • 合约和库应该使用驼峰式命名。例如,SmartContract, Owner等。
  • 合约和库名应该匹配它们的文件名。
  • 如果文件中有多个合约/库,请使用核心合约/库的名称。

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 – 函数

函数是一组可重用代码的包装,接受输入,返回输出。

函数定义

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(视图)函数不会修改状态。如果函数中存在以下语句,则被视为修改状态,编译器将抛出警告。

  • 修改状态变量。
  • 触发事件。
  • 创建合约。
  • 使用selfdestruct
  • 发送以太。
  • 调用任何不是视图函数或纯函数的函数
  • 使用底层调用
  • 使用包含某些操作码的内联程序集。

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()函数来还原潜在的状态更改。

声明纯函数,可以在函数声明里,添加pure关键字。

示例

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 – 函数重载

同一个作用域内,相同函数名可以定义多个函数。这些函数的参数(参数类型或参数数量)必须不一样。仅仅是返回值不一样不被允许。

下面的例子展示了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。

使用限制访问修饰符,我们可以限制谁能修改合约状态,或者调用合约函数等操作。

下面示例中,创建了多个修饰符:

  • onlyBy 限制可以调用该函数的调用者(根据地址)。
  • onlyAfter 限制该函数只能在特定的时间段之后调用。
  • costs 调用方只能在提供特定值的情况下调用此函数。。

示例

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中,合约继承的重要特点:

  • 派生合约可以访问父合约的所有非私有成员,包括内部方法和状态变量。但是不允许使用this
  • 如果函数签名保持不变,则允许函数重写。如果输出参数不同,编译将失败。
  • 可以使用super关键字或父合同名称调用父合同的函数。
  • 在多重继承的情况下,使用super的父合约函数调用,优先选择被最多继承的合约。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340