WEB3之路-- solidity学习笔记

Posted bit熊

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WEB3之路-- solidity学习笔记相关的知识,希望对你有一定的参考价值。

学习资料:GitHub - AmazingAng/WTFSolidity: 我最近在重新学solidity,巩固一下细节,也写一个“Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。

变量类型

  • 数值类型(Value Type):包括布尔型,整数型等等,这类变量赋值时候直接传递数值。
  • 引用类型(Reference Type):包括数组和结构体,这类变量占空间大,赋值时候直接传递地址(类似指针)。
  • 映射类型(Mapping Type)Solidity里的哈希表。
  • 函数类型(Function Type)Solidity文档里把函数归到数值类型,但我觉得他跟其他类型差别很大,所以单独分一类。

一、数值类型

1.布尔型(bool):true 或者 false

2.整型(uint):uint、uint8、uint16、uint32、uint256

        整型数值没有负数

3.地址类型(address):地址类型(address)存储一个 20 字节的值(以太坊地址的大小)

4.定长字节数组:字节数组bytes分两种,一种定长(bytebytes8bytes32),另一种不定长

5.枚举型(enum):枚举(enum)是solidity中用户定义的数据类型

二、引用类型

1.数组(Array):数组(Array)是solidity常用的一种变量类型,用来存储一组数据(整数,字节,地址等等)。数组分为固定长度数组和可变长度数组两种

数组成员:

  • length: 数组有一个包含元素数量的length成员,memory数组的长度在创建后是固定的。
  • push()动态数组bytes拥有push()成员,可以在数组最后添加一个0元素。
  • push(x)动态数组bytes拥有push(x)成员,可以在数组最后添加一个x元素。
  • pop()动态数组bytes拥有pop()成员,可以移除数组最后一个元素。

2.结构体(struct):Solidity支持通过构造结构体的形式定义新的类型

// 结构体用法
    struct Student
        uint256 id;
        uint256 score; 
    

三、映射类型

Mapping:声明映射的格式为mapping(_KeyType => _ValueType)

映射的规则:

  • 规则1:映射的_KeyType只能选择solidity默认的类型,比如uintaddress等,不能用自定义的结构体。
  • 规则2:映射的存储位置必须是storage,因此可以用于合约的状态变量,函数中的storage变量。不能用于public函数的参数或返回结果中,因为mapping记录的是一种关系 (key - value pair)。

  • 规则3:如果映射声明为public,那么solidity会自动给你创建一个getter函数,可以通过Key来查询对应的Value

  • 规则4:给映射新增的键值对的语法为_Var[_Key] = _Value,其中_Var是映射变量名,_Key_Value对应新增的键值对

四、函数类型

1.function:声明函数时的固定用法,想写函数,就要以function关键字开头。

2.(<parameter types>):圆括号里写函数的参数,也就是要输入到函数的变量类型和名字。

3.internal|external|public|private:函数可见性说明符,一共4种。没标明函数类型的,默认internal

  • public: 内部外部均可见。(也可用于修饰状态变量,public变量会自动生成 getter函数,用于查询数值).
  • private: 只能从本合约内部访问,继承的合约也不能用(也可用于修饰状态变量)。
  • external: 只能从合约外部访问(但是可以用this.f()来调用,f是函数名)
  • internal: 只能从合约内部访问,继承的合约可以用(也可用于修饰状态变量)。

4.[pure|view|payable]:决定函数权限/功能的关键字。

pure和view都不需要付gas

  • pure:不能读取也不能写入
  • view:只能读取
  • payable:可支付

5.[returns ()]:函数返回的变量类型和名称。

常量

一、constant

constant变量必须在声明的时候初始化,之后再也不能改变。尝试改变的话,编译不通过。

二、immutable

immutable变量可以在声明时或构造函数中初始化,因此更加灵活。

常用方法

1.修饰器(modifier)

修饰器(modifier)是solidity特有的语法,类似于面向对象编程中的decorator,声明函数拥有的特性,并减少代码冗余。

   // 定义modifier
   modifier onlyOwner 
      require(msg.sender == owner); // 检查调用者是否为owner地址
      _; // 如果是的话,继续运行函数主体;否则报错并revert交易
   

2.构造函数(constructor)

构造函数(constructor)是一种特殊的函数,每个合约可以定义一个,并在部署合约的时候自动运行一次。它可以用来初始化合约的一些参数,例如初始化合约的owner地址:

 address owner; // 定义owner变量

   // 构造函数
   constructor() public 
      owner = msg.sender; // 在部署合约的时候,将owner设置为部署者的地址
   

3.事件(events)

Solidity中的事件(event)是EVM上日志的抽象,它具有两个特点:

  • 响应:应用程序(ether.js)可以通过RPC接口订阅和监听这些事件,并在前端做响应。
  • 经济:事件是EVM上比较经济的存储数据的方式,每个大概消耗2,000-5,000 gas不等。相比之下,存储一个新的变量至少需要20,000 gas

事件的声明由event关键字开头,然后跟事件名称,括号里面写好事件需要记录的变量类型和变量名。以ERC20代币合约的Transfer事件为例:

event Transfer(address indexed from, address indexed to, uint256 value);

4.继承(inheritance)

规则

virtual: 父合约中的函数,如果希望子合约重写,需要加上virtual关键字。

override:子合约重写了父合约中的函数,需要加上override关键。

简单继承

contract Baba is Yeye

多重继承

contract Erzi is Yeye, Baba

修饰器的继承

用法同函数继承

构造函数的继承

  1. 在继承时声明父构造函数的参数,例如:contract B is A(1)
  2. 在子合约的构造函数中声明构造函数的参数

5.异常(errors)

error:方便高效省gas

error TransferNotOwner(); // 自定义error,在执行当中,error必须搭配revert(回退)命令使用。
  function transferOwner1(uint256 tokenId, address newOwner) public 
        if(_owners[tokenId] != msg.sender)
            revert TransferNotOwner();
        
        _owners[tokenId] = newOwner;
    

require:gas随着描述异常的字符串长度增加

使用方法:require(检查条件,”异常的描述”),当检查条件不成立的时候,就会抛出异常。

我们用require命令重写一下上面的transferOwner函数:

 function transferOwner2(uint256 tokenId, address newOwner) public 
        require(_owners[tokenId] == msg.sender, "Transfer Not Owner");
        _owners[tokenId] = newOwner;
    

assert命令一般用于程序员写程序debug,因为他不能解释抛出异常的原因(比require少个字符串)。他的用法很简单,assert(检查条件),当检查条件不成立的时候,就会抛出异常。

function transferOwner3(uint256 tokenId, address newOwner) public 
        assert(_owners[tokenId] == msg.sender);
        _owners[tokenId] = newOwner;
    

6.安全数学(SafeMath)

SafeMath用来防止溢出,有四个方法 — add, sub, mul, 以及 div。

using SafeMath for uint256;

uint256 a = 5;
uint256 b = a.add(3); // 5 + 3 = 8
uint256 c = a.mul(2); // 5 * 2 = 10

7.import

solidity支持利用import关键字导入其他源代码中的合约,让开发更加模块化。

// 通过文件相对位置import
import './Yeye.sol';

8.接收ETH

Solidity支持两种特殊的回调函数,receive()fallback(),他们主要在两种情况下被使用:

  1. 接收ETH
  2. 处理合约中不存在的函数调用(代理合约proxy contract

接收ETH函数 receive

receive()只用于处理接收ETH。一个合约最多有一个receive()函数,声明方式与一般函数不一样,不需要function关键字:receive() external payable ...

receive()函数不能有任何的参数,不能返回任何值,必须包含externalpayable

当合约接收ETH的时候,receive()会被触发。

回退函数 fallback

fallback()函数会在调用合约不存在的函数时被触发。可用于接收ETH,也可以用于代理合约proxy contractfallback()声明时不需要function关键字,必须由external修饰,一般也会用payable修饰,用于接收ETH:fallback() external payable ...

receivefallback都能够用于接收ETH,他们触发的规则如下:

触发fallback() 还是 receive()?
           接收ETH
              |
         msg.data是空?
            /  \\
          是    否
          /      \\
receive()存在?   fallback()
        / \\
       是  否
      /     \\
receive()   fallback()

9.发送ETH

transfer

  • 用法是transfer(发送ETH数额)
  • transfer()gas限制是2300,足够用于转账,但对方合约的fallback()receive()函数不能实现太复杂的逻辑。
  • transfer()如果转账失败,会自动revert(回滚交易)。

send

  • 用法是send(发送ETH数额)
  • send()gas限制是2300,足够用于转账,但对方合约的fallback()receive()函数不能实现太复杂的逻辑。
  • send()如果转账失败,不会revert
  • send()的返回值是bool,代表着转账成功或失败,需要额外代码处理一下。

call

  • 用法是callvalue: 发送ETH数额("")
  • call()没有gas限制,可以支持对方合约fallback()receive()函数实现复杂逻辑。
  • call()如果转账失败,不会revert
  • call()的返回值是(bool, data),其中bool代表着转账成功或失败,需要额外代码处理一下。

10.call

call 是address类型的低级成员函数,它用来与其他合约交互。它的返回值为(bool, data),分别对应call是否成功以及目标函数的返回值。

call的使用规则

call的使用规则如下:

目标合约地址.call(二进制编码);

其中二进制编码利用结构化编码函数abi.encodeWithSignature获得:

abi.encodeWithSignature("函数签名", 逗号分隔的具体参数)

函数签名"函数名(逗号分隔的参数类型)"。例如abi.encodeWithSignature("f(uint256,address)", _x, _addr)

另外call在调用合约时可以指定交易发送的ETH数额和gas

目标合约地址.callvalue:发送ETH数额, gas:gas数额(二进制编码);

11.ABI编码解码

abi.encode

将给定参数利用ABI规则编码。ABI被设计出来跟智能合约交互,他将每个参数转填充为32字节的数据,并拼接在一起。

abi.decode

abi.decode用于解码abi.encode生成的二进制编码,将它还原成原本的参数。

12.函数选择器(Selector)

 selector定义为函数签名的哈希的前4个字节

函数签名,为"函数名(逗号分隔的参数类型)"。举个例子,mint的函数签名为"mint(address)"。在智能合约中,不同的函数有不同的函数签名,因此我们可以通过函数签名来确定要调用哪个函数。

13.try-catch

solidity中,try-catch只能被用于external函数或创建合约时constructor(被视为external函数)的调用。基本语法如下:

        try externalContract.f() 
            // call成功的情况下 运行一些代码
         catch 
            // call失败的情况下 运行一些代码
        

其中externalContract.f()时某个外部合约的函数调用,try模块在调用成功的情况下运行,而catch模块则在调用失败时运行。

应用

1.ERC20

IERC20ERC20代币标准的接口合约,规定了ERC20代币需要实现的函数和事件。

事件

IERC20定义了2个事件:Transfer事件和Approval事件,分别在转账和授权时被释放

    /**
     * @dev 释放条件:当 `value` 单位的货币从账户 (`from`) 转账到另一账户 (`to`)时.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev 释放条件:当 `value` 单位的货币从账户 (`owner`) 授权给另一账户 (`spender`)时.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

函数

IERC20定义了6个函数,提供了转移代币的基本功能,并允许代币获得批准,以便其他链上第三方使用。

  • totalSupply()返回代币总供给
  • balanceOf()返回账户余额
  • transfer()转账
  • allowance()返回授权额度
  • approve()授权
  • transferFrom()授权转账

2.erc721

IERC721事件

IERC721包含3个事件,其中TransferApproval事件在ERC20中也有。

  • Transfer事件:在转账时被释放,记录代币的发出地址from,接收地址totokenid
  • Approval事件:在授权时释放,记录授权地址owner,被授权地址approvedtokenid`。
  • ApprovalForAll事件:在批量授权时释放,记录批量授权的发出地址owner,被授权地址operator和授权与否的approved

IERC721函数

  • balanceOf:返回某地址的NFT持有量balance
  • ownerOf:返回某tokenId的主人owner
  • transferFrom:普通转账,参数为转出地址from,接收地址totokenId
  • safeTransferFrom:安全转账(如果接收方是合约地址,会要求实现ERC721Receiver接口)。参数为转出地址from,接收地址totokenId
  • approve:授权另一个地址使用你的NFT。参数为被授权地址approvetokenId
  • getApproved:查询tokenId被批准给了哪个地址。
  • setApprovalForAll:将自己持有的该系列NFT批量授权给某个地址operator
  • isApprovedForAll:查询某地址的NFT是否批量授权给了另一个operator地址。
  • safeTransferFrom:安全转账的重载函数,参数里面包含了data

3.erc1155

面是ERC1155的元数据接口合约IERC1155MetadataURI

/**
 * @dev ERC1155的可选接口,加入了uri()函数查询元数据
 */
interface IERC1155MetadataURI is IERC1155 
    /**
     * @dev 返回第`id`种类代币的URI
     */
    function uri(uint256 id) external view returns (string memory);

IERC1155事件

  • TransferSingle事件:单类代币转账事件,在单币种转账时释放。
  • TransferBatch事件:批量代币转账事件,在多币种转账时释放。
  • ApprovalForAll事件:批量授权事件,在批量授权时释放。
  • URI事件:元数据地址变更事件,在uri变化时释放。

IERC1155函数

  • balanceOf():单币种余额查询,返回account拥有的id种类的代币的持仓量。
  • balanceOfBatch():多币种余额查询,查询的地址accounts数组和代币种类ids数组的长度要相等。
  • setApprovalForAll():批量授权,将调用者的代币授权给operator地址。。
  • isApprovedForAll():查询批量授权信息,如果授权地址operatoraccount授权,则返回true
  • safeTransferFrom():安全单币转账,将amount单位id种类的代币从from地址转账给to地址。如果to地址是合约,则会验证是否实现了onERC1155Received()接收函数。
  • safeBatchTransferFrom():安全多币转账,与单币转账类似,只不过转账数量amounts和代币种类ids变为数组,且长度相等。如果to地址是合约,则会验证是否实现了onERC1155BatchReceived()接收函数。

以上是关于WEB3之路-- solidity学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

solidity 语法学习

Web3 + 区块链 学习计划

《solidity学习笔记》chapter 1-solidity基础知识

《solidity学习笔记》chapter 3-solidity其他知识

Solidity 学习笔记

solidity学习笔记02-数据存储篇