Renaissance Labs

Posted on Mar 01, 2022Read on Mirror.xyz

Learn Solidity Series 8: contract event

1. create contract instance

创建合约的语法:

new 合约名();

示例:
contract A {}
contract B {
    function test() public { 
        A a = new A();
    }
}

创建合约实例的时候,默认会调用构造函数,并执行构造函数中的代码。如果没有定义构造函数,EVM编译器会自动生成一个默认的构造函数。如果构造函数指定了参数,那么创建合约实例时候需要传入相同数量的实参。

contract A {
    int num;
    constructor(int _num) public {
        num = _num;
    }
}

contract B { 
    function test() public { 
        A a = new A(10);
    }
}

注意:合约构造函数是可选的。solidity不支持构造函数的重载,也就是一个合约最多只有一个构造函数。

2. contract inherit

一个合约可以继承另外一个合约,也可以同时继承多个合约

contract A {}
contract B is A {}

如果同时继承了多个父合约,最远的派生函数会被调用。


contract C is A, B {}

contract SimpleContract {
    function test() public returns(uint) {
        C c = new C();
        c.a(); // 20
        c.f(); // 20
    }
}

上面合约C同时继承了A和B,因为B合约比A合约距离C合约远,所以在test函数中调用父合约状态变量和函数,实际调用的是B合约。

父合约的函数可以被派生合约中具有相同名称、相同参数的函数重载。如果派生合约的重载函数的返回值类型或数量不相同,会导致错误。

如果父合约构造函数带参数,那么可以在派生合约声明时候指定参数,或者也可以在构造函数位置以修饰器方式提供。

contract A {
    int num;
    constructor(int _num) public {
        num = _num;
    }
}

contract B is A {}
contract B is A(10) {}
contract B is A {
    constructor() public A(10) {}
}

3. abstract contract

如果一个派生合约没有指定所有父合约的构造函数参数,那么该合约是一个抽象合约。


contract A {
    uint a;
    constructor(uint _a) internal {
        a = _a;
    }
}

contract B is A {}

contract SimpleContract {
    function test() public { 
        //B a = new B();
    }
}

因为合约B继承合约A时候,没有指定构造函数参数,因此合约B是一个抽象合约。抽象合约不能够实例化,因此上面test函数中注释代码会报错。

另外,如果一个合约包含未实现的函数,那么该合约也是一个抽象合约。

contract A {
    function test() public;
}

4. interface

接口主要用于规范合约的实现,其定义格式为:

定义接口使用interface关键字。接口里面只能够声明函数,不能定义状态变量和构造函数,也不能够对函数提供实现。

interface InterfaceA {
    function f() external;
}

上面接口函数f()必须要使用external修饰符。与Java不同,solidity接口之间无法继承。

一个合约可以继承多个接口。

interface InterfaceA {}
interface InterfaceB {}
interface Impl is InterfaceA, InterfaceB {}

如果一个合约没有实现接口里面的所有函数,那么该合约是一个抽象合约。

👉抽象合约与接口的相同点和不同点?

1)抽象合约可以定义状态变量和构造函数,而接口不可以;

2)抽象合约中的函数可以不实现,也可以实现,而接口中只能包含未实现的函数;

3)抽象合约中的函数可以使用任意修饰符,但接口中函数只能使用external修饰符;

4)抽象合约可以继承其他合约或接口,而接口不能;

5)抽象合约和接口都不能实例化;

6)抽象合约和接口都可以声明结构体和枚举;

5. lib合约

库合约不同于一般合约,它更像Java里的工具类,负责为其他合约提供有用的工具函数。

定义语法:

library 库合约 {}

库合约可以定义结构体、枚举,也可以声明状态变量,但是状态变量必须是常量。

像一般函数调用一样,库合约中的成员可以通过库合约.成员方式访问。

library Utils {
    function sum(uint a, uint b) public pure returns(uint) {
        return a + b;
    }
}

library SimpleContract {
    function test() public pure {
        uint result = Utils.sum(10, 20);
    }
}

如果在合约中包含了库合约,那么部署合约时候,EVM会自动将库合约代码包含进来,无需额外部署库合约。

除了上面调用方式以外,还可以通过using...for语法访问库合约里面的函数。该语法运行将库合约中的函数附加到任意类型上,从而实现类似原生函数调用的效果。

library Utils {
    function get(int[] storage self, uint index) internal view returns(int) {
	    require(index >= 0);
	    return self[index];
    }
    
    function get(mapping(address => uint128) storage self) internal view returns(uint128) {
	    require(msg.sender > address(0));
	    return self[msg.sender];
    }
}

contract SimpleContract {
    int[] ids;
    mapping(address => uint128) balances;
    using Utils for int[];
    using Utils for mapping(address => uint128);
    
    function test() public view  {
	    int id = ids.get(0);
	    uint128 bal = balances.get();
    }
}

上面库合约定义了两个重载方法。当使用using..for语法访问库合约时候,第一个参数为对应类型的状态变量。

上面代码通过using...for语法,将库合约绑定到uint[]和mapping(address => uint128)类型。因此,该合约中所有uint[]和mapping(address => uint128)类型的变量都可以库函数,这时候库函数的第一个参数引用了调用该函数的变量。

6.Event

事件使得开发者可以访问EVM的日志系统,帮助开发者了解智能合约运行过程的状态信息。开发者可以在dapp中监听solidity事件,当智能合约中触发了某个事件,EVM的日志系统会反过来调用dapp中定义的事件回调函数,从而在回调函数中将日志信息打印出来。

记录在区块链上的日志信息可以被外部检索出来。solidity规定一个事件里面最多有3个参数可以设置为indexed,表示用户可以通过该参数对搜索结果进行过滤。所有非indexed参数将保存在日志的数据部分,而indexed参数不会被保存。

contract SimpleContract {
    event LogSave(
	address indexed _addr,
	uint _value
    );

    function saveMoney() public payable {
	if (msg.value <= 100 wei) {
	    emit LogSave(msg.sender, msg.value);
        }
    }
}

上面合约定义了一个LogSave事件,其中第一个参数是可索引参数。在saveMoney函数中,判断如果msg.value小于等于100 wei,则触发LogSave事件。