Solidity学习笔记3


Solidity笔记

原项目参考:https://github.com/AmazingAng/WTF-Solidity


12、事件

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

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

12.1、声明事件

  • event关键字开头,接着是事件名称,括号里面写好事件需要记录的变量类型和变量名。以ERC20代币合约的Transfer事件为例:
// Transfer事件共记录了3个变量from,to和value,分别对应代币的转账地址,接收地址和转账数量,其中from和to前面带有indexed关键字,他们会保存在以太坊虚拟机日志的topics中,方便之后检索。
event Transfer(address indexed from, address indexed to, uint256 value);

12.2、释放事件

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

mapping(address => uint256) public _balances;
// 用_transfer()函数进行转账操作的时候,都会释放Transfer事件,并记录相应的变量
function _transfer(address from, address to, uint256 amount) external {
	_balances[from] = 10000000;
	_balances[from] -= amount;
	_balances[to] += amount;
	
	// 释放事件
	emit Transfer(from, to, amount);
}

12.3、EVM日志Log

  • 以太坊虚拟机(EVM)用日志Log来存储Solidity事件,每条日志记录都包含主题topics和数据data两部分。

12.4、主题topics

日志的第一部分是主题数组,用于描述事件,长度不能超过4。它的第一个元素是事件的签名(哈希)。对于上面的Transfer事件,它的签名就是:

keccak256("Transfer(address,address,uint256)")

//0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef

除了事件签名,主题还可以包含至多3indexed参数,也就是Transfer事件中的fromto

indexed标记的参数可以理解为检索事件的索引“键”,方便之后搜索。每个 indexed 参数的大小为固定的256比特,如果参数太大了(比如字符串),就会自动计算哈希存储在主题中。

12.5、数据data

事件中不带 indexed的参数会被存储在 data 部分中,可以理解为事件的“值”。data 部分的变量不能被直接检索,但可以存储任意大小的数据。因此一般 data 部分可以用来存储复杂的数据结构,例如数组和字符串等等,因为这些数据超过了256比特,即使存储在事件的 topics 部分中,也是以哈希的方式存储。另外,data 部分的变量在存储上消耗的gas相比于 topics 更少。


13、继承

继承是面向对象编程很重要的组成部分,可以显著减少重复代码。如果把合约看作是对象的话,solidity也是面向对象的编程,也支持继承。

13.1、规则

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

注意:用override修饰public变量,会重写与变量同名的getter函数,例如:

mapping(address => uint256) public override balance0f;

13.2、简单继承

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

// 先写一个简单合约
contract Yeye {
	event Log(string msg);
	
    // 定义三个function hip() pop() man()
    function hip() public virtual {
        emit Log("Yeye");
    } 

    function pop() public virtual {
        emit Log("Yeye");
    }

    function yeye() public virtual {
        emit Log("Yeye");
    }
}

// 再定义一个子合约,继承Yeye
contract Baba is Yeye {
    // 使用override重写hip()和pop()
    function hip() public virtual override {
        emit Log("Baba");
    }

    function pop() public virtual override {
        emit Log("Babe");
    }
    
    function foo() public virtual {
        emit Log("Baba");
    }
    
    function baba() public virtual {
        emit Log("Baba");
    }
}

13.3、多重继承

  • 继承时要按辈分最高到最低的顺序排。比如我们写一个Erzi合约,继承Yeye合约和Baba合约,那么就要写成contract Erzi is Yeye, Baba,而不能写成contract Erzi is Baba, Yeye,不然就会报错。
  • 如果某一个函数在多个继承的合约里都存在,比如例子中的hip()pop(),在子合约里必须重写,不然会报错。
  • 重写在多个父合约中都重名的函数时,override关键字后面要加上所有父合约名字,例如override(Yeye, Baba)
// 再定义一个子合约,继承上面两个合约
contract Erzi is Yeye, Baba {
    function hip() public virtual override (Yeye, Baba) {
        emit Log("Erzi");
    }

    function pop() public virtual override (Yeye, Baba) {
        emit Log("Erzi");
    }

    function foo() public virtual override (Baba) {
        emit Log("Erzi"); 
    }
}

13.4、修饰器的继承

  • 修饰器(Modifier)同样可以继承,用法与函数继承类似,在相应的地方加virtualoverride关键字即可。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

contract Base1 {
    modifier exactDividedBy2And3(uint _a) virtual {
        require(_a % 2== 0 && _a % 3 == 0);
        _;
    }
}

contract Identifier is Base1 {
    // 一个分别被2和3整除的值,传入时需要经过检查
    function getDivided2a3(uint _num) public exactDividedBy2And3(_num) pure returns(uint, uint) {
        return getDivided2a3NoModifier(_num);
    }

    function getDivided2a3NoModifier(uint _num) public pure returns(uint, uint) {
        uint d1 = _num / 2;
        uint d2 = _num / 3;
        return (d1, d2);
    }
}

Identifier合约可以直接在代码中使用父合约中的exactDividedBy2And3修饰器,也可以利用override关键字重写修饰器:

contract Identifier is Base1 {
    modifier exactDividedBy2And3(uint _a) virtual override {
        require(_a % 2==0);
        _;
    }
    
    // ...
}

13.5、构造函数的继承

子合约有两种方法继承父合约构造函数

  • 在继承时声明父构造函数的参数,例如:contract B is A(1)
  • 在子合约的构造函数中声明构造函数的参数,例如:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

// 父合约A有一个状态变量a
abstract contract A {
    uint public a;

    constructor(uint _a) {
        a = _a;
    }
}

contract C is A {
    constructor(uint _c) A(_c * _c) {}
}

// 如果派生合约未能给其继承的基合约指定构造函数参数时,那么,该派生合约必须声明为抽象合约(abstract contract)。
// 在 Solidity 0.8.x版本以上,抽象合约的抽象函数需加上virtual修饰,而对于的派生合约中的函数实现也得加上override修饰,否则编译过不了。
abstract contract Animal {
    function eat() public virtual;
}

contract Cat is Animal {
    function eat() public override {}
}

13.6、调用父合约的函数

子合约有两种方式调用父合约的函数,直接调用和利用super关键字。

  • 直接调用:子合约可以直接用父合约名.函数名()的方式来调用父合约函数,例如Yeye.pop()
function callParent() public{
	Yeye.pop();
}
  • super关键字:子合约可以利用super.函数名()来调用最近的父合约函数。solidity继承关系按声明时从右到左的顺序是:contract Erzi is Yeye, Baba,那么Baba是最近的父合约,super.pop()将调用Baba.pop()而不是Yeye.pop()
function callParentSuper() public{
    // 将调用最近的父合约函数,Baba.pop()
    super.pop();
}

13.7、钻石继承

  • 在面向对象编程中,钻石继承(菱形继承)指一个派生类同时有两个或两个以上的基类。

  • 在多重+菱形继承链条上使用super关键字时,需要注意的是使用super会调用继承链条上的每一个合约的相关函数,而不是只调用最近的父合约。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/* 继承树:
  Animal
 /  \
pig dog
 \  /
people
*/
contract Animal {
    event Log(string message);

    function foo() public virtual {
        emit Log("Animal.foo called");
    }

    function bar() public virtual {
        emit Log("Animal.bar called");
    }
}

contract Pig is Animal {
    function foo() public virtual override {
        emit Log("Pig.foo called");
    }

    function bar() public virtual override {
        emit Log("Pig.bar called");
        super.bar();
    }
}

contract Dog is Animal {
    function foo() public virtual override {
        emit Log("Dog.foo called");
        Animal.foo();
    }

    function bar() public virtual override {
        emit Log("Dog.bar called");
        super.bar();
    }
}

contract People is Pig, Dog {
    function foo() public virtual override(Pig, Dog) {
        super.foo();
    }

    function bar() public virtual override(Pig, Dog) {
        super.bar();
    }
}

文章作者: ye
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 ye !
  目录