区块链
Solidity

介绍

一文聊透 Solidity 语法:助你成为智能合约专家 (opens in a new tab) Solidity教程:初学Solidity (opens in a new tab) 僵尸教程 (opens in a new tab) solidity官方文档 (opens in a new tab)

环境搭建

https://remix.ethereum.org/ (opens in a new tab) http://remix.zhiguxingtu.com/ (opens in a new tab) npm install -g solc 安装 Solidity 编译器 solc solcjs --version

基础语法

/**一个 Solidity 源文件可以包含任意数量的合约定义、import指令和pragma指令

  • pragma指令只对自己的源文件起作用
  • import导入文件导入所有全局符号;创建一个新的全局符号symbolName,它的成员都是来自“filename”的全局符号;要从当前目录导入文件x,如果不指定当前路径,可能会在全局“include目录”中引用另一个文件
  • contract智能合约智能合约是位于以太坊区块链上特定地址的代码(函数)和数据(状态)的集合 */
demo.js
//pragma solidity ^0.4.0; 表示不超过0.5.0版本,即0.4.0 ~ 0.4.9 之间
pragma solidity >=0.8.0;
// import "filename";
// import * as symbolName from "filename";
// import "./x";
// uint storedData;,声明了一个名为storedData的状态变量,类型为uint, set和get函数可用于修改或检索变量的值
contract SimpleStorage {
    uint storedData;
    function set(uint x) public {
        storedData = x;
    }
    function get() public view returns (uint) {
        return storedData;
    }
}

特殊变量和函数

区块和交易属性 blockhash(uint blockNumber) returns (bytes32):指定区块的区块哈希——仅可用于最新的 256 个区块且不包括当前区块 block.chainid (uint): 当前链 id block.coinbase ( address ): 挖出当前区块的矿工地址 block.difficulty ( uint ): 当前区块难度 block.gaslimit ( uint ): 当前区块 gas 限额 block.number ( uint ): 当前区块号 block.timestamp ( uint): 自 unix epoch 起始当前区块以秒计的时间戳 gasleft() returns (uint256) :剩余的 gas msg.data ( bytes ): 完整的 calldata msg.sender ( address ): 消息发送者(当前调用) msg.sig ( bytes4 ): calldata 的前 4 字节(也就是函数标识符) msg.value ( uint ): 随消息发送的 wei 的数量 tx.gasprice (uint): 交易的 gas 价格 tx.origin (address payable): 交易发起者(完全的调用链)

ABI 编码及解码函数 abi.decode(bytes memory encodedData, (...)) returns (...): 对给定的数据进行ABI解码,而数据的类型在括号中第二个参数给出 。 例如: (uint a, uint[2] memory b, bytes memory c) = abi.decode(data, (uint, uint[2], bytes)) abi.encode(...) returns (bytes): ABI - 对给定参数进行编码 abi.encodePacked(...) returns (bytes):对给定参数执行 紧打包编码 ,注意,可以不明确打包编码。 abi.encodeWithSelector(bytes4 selector, ...) returns (bytes): ABI - 对给定第二个开始的参数进行编码,并以给定的函数选择器作为起始的 4 字节数据一起返回 abi.encodeWithSignature(string signature, ...) returns (bytes):等价于 abi.encodeWithSelector(bytes4(keccak256(signature), ...)

数学和密码学函数 addmod(uint x, uint y, uint k) returns (uint)计算 (x + y) % k,加法会在任意精度下执行,并且加法的结果即使超过 2**256 也不会被截取。从 0.5.0 版本的编译器开始会加入对 k != 0 的校验(assert)。

mulmod(uint x, uint y, uint k) returns (uint)计算 (x * y) % k,乘法会在任意精度下执行,并且乘法的结果即使超过 2**256 也不会被截取。从 0.5.0 版本的编译器开始会加入对 k != 0 的校验(assert)。 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 = 签名的第2个32 字节 v = 签名的最后一个字节 ecrecover 返回一个 address, 而不是 address payable 。他们之前的转换参考 address payable ,如果需要转移资金到恢复的地址。

地址成员 <address>.balance (uint256) 以 Wei 为单位的 地址类型 Address 的余额。 <address>.code (bytes memory) 在 地址类型 Address 上的代码(可以为空) <address>.codehash (bytes32) :ref:address的codehash <address payable>.transfer(uint256 amount) 向 地址类型 Address 发送数量为 amount 的 Wei,失败时抛出异常,使用固定(不可调节)的 2300 gas 的矿工费。 <address payable>.send(uint256 amount) returns (bool) 向 地址类型 Address 发送数量为 amount 的 Wei,失败时返回 false,发送 2300 gas 的矿工费用,不可调节。 <address>.call(bytes memory) returns (bool, bytes memory) 用给定的有效载荷(payload)发出低级 CALL 调用,返回成功状态及返回数据,发送所有可用 gas,也可以调节 gas。 <address>.delegatecall(bytes memory) returns (bool, bytes memory) 用给定的有效载荷 发出低级 DELEGATECALL 调用 ,返回成功状态并返回数据,发送所有可用 gas,也可以调节 gas。 发出低级函数 DELEGATECALL,失败时返回 false,发送所有可用 gas,可调节。 <address>.staticcall(bytes memory) returns (bool, bytes memory) 用给定的有效载荷 发出低级 STATICCALL 调用 ,返回成功状态并返回数据,发送所有可用 gas,也可以调节 gas。

test
pragma solidity ^0.4.0;
 
contract ArrayContract {
    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;
    }
}
 
contract Test {   
   function callAddMod() public pure returns(uint){
      return addmod(4, 5, 3);
   }
   function callMulMod() public pure returns(uint){
      return mulmod(4, 5, 3);
   }
   function callKeccak256() public pure returns(bytes32 result){
      return keccak256("ABC");
   }
}

运算符

& (位与),对其整数参数的每个位执行位与操作。 | (位或),对其整数参数的每个位执行位或操作。 ^ (位异或),对其整数参数的每个位执行位异或操作。 ~ (位非),一元操作符,反转操作数中的所有位。 << (左移位)),将第一个操作数中的所有位向左移动,移动的位置数由第二个操作数指定,新的位由0填充。将一个值向左移动一个位置相当于乘以2,移动两个位置相当于乘以4,以此类推。 >> (右移位),左操作数的值向右移动,移动位置数量由右操作数指定

(A & B) // 2
(A | B) // 3
(A ^ B) // 1.
(~B) // -4.
(A << 1) // 4
(A >> 1) // 1

错误处理与异常

/**

  • assert(bool condition) − 如果不满足条件,此方法调用将导致一个无效的操作码,对状态所做的任何更改将被还原。这个方法是用来处理内部错误的。
  • require(bool condition, string memory message) − 如果不满足条件,此方法调用将恢复到原始状态。此方法用于检查输入或外部组件的错误。它提供了一个提供自定义消息的选项。
  • revert(string memory reason) − 此方法将中止执行并将所做的更改还原为执行前状态。它提供了一个提供自定义消息的选项。
  • try/catch 捕获外部调用的异常。 */
pragma solidity ^0.5.0;

contract Vendor {
   address public seller;
   modifier onlySeller() {
      require(msg.sender == seller, "Only seller can call this.");
      _;
   }
   function sell(uint amount) public payable onlySeller { 
      if (amount > msg.value / 2 ether)
         revert("Not enough Ether provided.");
      // 执行销售操作
   }
    function buy1(address payable addr) public {
        addr.transfer(1 ether);
        assert(addr.balance > 1 ether);
    }
    function buy(uint amount) public {
        require(amount < 1, "amount must be greater than 1");
    }
    // function buy1(uint amount) public {
    //     if(amount < 1) {
    //         revert(amount > 1, "amount must be greater than 1"); 
    //     }
    // }
}

数据存储位置

/**所有引用类型的数据(包括数组、结构体、mapping、合约等)都有三种存储位置。分别是:

  • memory内存 :合约执行时的内存。申明在函数中用于缓存临时变量、返回值或public函数。
  • storage存储 :合约的永久存储。直接声明在合约中的变量或iternal函数。
  • calldata调用数据 :不可修改的非持久性数据位置,external外部函数的传参。似memory但位置不同。
  • stack堆栈是由EVM (Ethereum虚拟机)维护的非持久性数据。EVM使用堆栈数据位置在执行期间加载变量。堆栈位置最多有1024个级别的限制
  • 在 storage 和 memory/calldata 之间进行复制,会创建独立的拷贝。memory 和 calldata 之间相互赋值不会创建拷贝,而是创建引用。
  • storage 与本地 storage 之间的赋值也只会创建引用。
  • 对于引用类型的局部变量,从一个内存变量复制到另一个内存变量不会创建副本。 */
pragma solidity ^0.5.0;  

contract MyContract {
  uint256[] arr1; // arr1 存储在 storage 中
  
  function fn1(uint256[] memory arr2) public {
      arr1 = arr2;// memory 赋值到 storage 中,创建拷贝
      uint256[] storage arr4 = arr1;// stoarge 赋值到 本地 storage 中,创建引用
      arr4.pop(); // pop 会同时影响 arr1
      delete arr1; // 清空 arr1,同时会影响 arr4
      // storage 是静态分配内存,所以不可以直接从 memory 赋值到本地 storage 中
      // arr4 = arr2;
      // 因为没有指向存储位置,所以无法重置指针
      // delete arr4;
      fn3(arr1); // storage 之间传递引用
      fn4(arr1);  // storage 到 memory 会拷贝
  }
  
  function fn2(uint256[] calldata arr3) external {}

  function fn3(uint256[] storage arr5) internal pure {}

  function fn4(uint256[] memory arr6) public pure {}

}

单位

以太单位是以太坊独有的单位,在其他编程语言中没有这种单位。

1 wei 等于 1。
1 gwei 等于 1e9。
1 ether = 1e18。
用代码表示如下:
assert(1 wei == 1);
assert(1 gwei == 1e9);
assert(1 ether == 1e18);

时间单位,默认 1 等于 1 秒。solidity 支持以下时间单位:

seconds:秒
minutes:分
hours:时
days:天
weeks:周
years:年,不推荐使用。

用代码表示如下:
assert(1 seconds == 1);
assert(1 minutes == 60 seconds);
assert(1 hours == 60 minutes);
assert(1 days == 24 hours);
assert(1 weeks == 7 days);