Renaissance Labs

Posted on Mar 01, 2022Read on Mirror.xyz

Learn Solidity Series 7: function

function define

函数入参和出参类型不能够使用var关键字。另外,函数可以返回多个值。

如果参数没有被使用,那么可以省略参数名。但是这些参数仍然存在堆栈中,只是无法访问而已。

function 函数名(参数类型 参数名, ...) 修饰符 [returns(返回类型, ...)] {
    函数体
}

示例:
function sum(int a, int b) public pure returns(int) {
   return a + b;
}

function test1(int a, int b) public pure returns(int, int) {
   return (a, b);
}

function test2(int a, int b) public pure returns(int x, int y) {
   x = a;
   y = b;
}
function test(int a, int) public pure returns(int) {
  return a;
}

函数声明使用pure,代表该函数不能读取或修改状态变量,也不能发送和接收以太币,只能调用其他pure函数

除了pure以外,还可以使用view来修饰函数,这时候该函数只能够读取状态变量,而不能修改状态变量,也不能发送或接收以太币,只能调用其他pureview函数。

function test(int a, int) public pure returns(int) {
  return a;
}

function call

函数调用可以分为内部调用和外部调用。内部调用就是通过函数名直接调用,外部调用是通过合约实例进行调用。

如果是内部调用,被调用函数在EVM中被解释为简单的代码跳转。而外部函数调用是通过发送消息的方式进行调用。

function sum(int a, int b) public pure returns(int) {
  return a + b;
}

function test() public {
   int result = sum(10, 20); // 内部调用
   int result2 = this.sum(10, 20); // 外部调用
}

如果函数使用payable修饰,那么调用该函数时候可以指定花费的钱和gas数量。

上面调用合约a的test函数时,指定了花费的wei和gas数量。如果被调用函数所在合约本身发生抛出异常,或gas用完等情况,函数调用会抛出异常。

注意:当调用其他合约时候,就相当于将控制权交给了被调用的合约。如果不清楚被调用合约的实现细节,这时候会存在很大风险(比如重入攻击)。

contract A {
    function test() payable public {
        // TODO
    }
}

contract B {
    A a;
    
    function setA(A _a) public {
        a = _a;
    }
    
    function call() public {
        a.test.value(10).gas(100)();
    }
}

function overload

函数重载是指在一个合约里面同时存在两个或两个以上同名的函数,但函数的参数类型或数量不一样。

当程序调用函数f时候,会根据传入参数的类型和数量决定调用哪个函数。如果传入参数可以隐式地转换为目标函数参数类型,这时候需要明确指明传入参数的类型。

contract A {
   function f() public {}
   function f(uint a) public {}
   function f(uint a, uint b) public {}
}

function visable

solidityi提供了四种不同的可见性类型,用于限定函数或状态变量的访问范围。

可见性类型 作用 external 允许该函数从其他合约和交易中调用,不能在当前合约直接调用,但可以通过this调用 public 允许该函数在合约内部和外部调用 internal 只允许在合约内部或子合约中调用 private 只能够在当前合约中调用

getter function

对于public修饰的状态变量,solidity编译器会自动为该变量添加对应的getter函数,getter函数名就是状态变量的名称。

getter函数的可见性为external,也就是在合约内部无法直接调用,只能通过this或合约外部调用。另外getter函数默认被标记为view,也就是只能读取状态变量。

contract A { 
    uint public data;
}

contract B {
    function test() public {  
        A a = new A();
        a.data;
        a.data();
    }
}
contract A { 
    uint public data;
}

contract B {
    function test() public {  
        A a = new A();
        a.data;
        a.data();
    }
}
//a.data()是通过getter函数方法data变量

对于数组类型的public状态变量,如果要获取整个数组的话,不能够通过getter函数获取,只能通过变量名,或者定义一个函数来返回该变量的方式来获取。

contract A {
    uint[] public data;  
    function getArray() public view returns(uint[] memory) { 
        return data;
    }
}

contract B {
    function test() public { 
        A a = new A();
        a.data;
        a.getArray();
        //a.data();
    }
}

上面test函数的最后一行注释的代码会提示TypeError: Wrong argument count for function call: 0 arguments given but expect 1。这里只能够通过getter函数获取数组中的元素,而不能获取整个数组。

function modifier

函数修饰器用于在执行函数之前执行其他额外的操作,最常见用法是在执行函数前检查是否满足条件,如果满足则执行目标函数。

定义修饰器的语法:

modifier 修饰器名称 {
    _;
}

下面代码定义了一个修饰器onlyOwner,然后在test函数声明时候指定了该修饰器。那么,当调用该函数时候,会先执行修饰器中的代码,然后再执行test函数中的代码。

contract SimpleContract {
    address owner;
    
    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }
    
    function test() public onlyOwner {}	
}

一个合约中可以定义多个修饰器。同一个函数也可以使用多个修饰器,多个修饰器之间使用空格隔开。调用函数时多个修饰器会依次执行。

contract SimpleContract {
    address owner;
    
    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }
    
    modifier onlyOwner2 {
        require(msg.sender == owner);
        _;
    }   

    function test() public onlyOwner onlyOwner2  {} 
}

与函数类似的是,修饰器也可以有参数。

modifier onlyOwner(uint a) {}

function test() public onlyOwner(10) {}

global function

solidity自带了一些特殊的变量和函数。这些变量和函数存在于全局的命名空间里面,主要用于提供区块链相关的信息,以及常用的工具函数。

1.区块和交易属性:

函数名 描述 blockhash(uint blockNum) returns(byte32) 获取最新256个区块中某个区块的哈希 block.chainid(uint) 获取当前区块链的ID block.coinbase(address payable) 获取当前区块矿工的地址 block.difficulty(uint) 获取当前区块的难度值 block.gaslimit(uint) 获取当前区块的gas限额 block.number(uint) 获取当前区块号 block.timestamp(uint) 获取当前区块的时间戳(以秒为单位) gasleft() returns(uint256) 返回剩余的gas数量 msg.data(bytes calldata) 获取完整的calldata msg.sender(address) 获取当前消息调用的发送者 msg.sig(bytes4) 获取函数签名 msg.value(uint) 获取发送消息所需要wei的数量 tx.gasprice(uint) 获取交易的油价 tx.origin(address) 获取交易的发起者 2.abi编解码函数:

函数名 描述 abi.decode(bytes memory encodedData, …) returns(…) 对给定数据进行abi解码,而类型由第二个参数来指定 abi.encode(…) returns(bytes memory) 对指定参数进行abi编码 abi.encodePacked(…) returns(bytes memory) 对指定参数进行压缩编码 abi.encodeWithSelector(bytes4 selector, …) returns(bytes memory) 从第二个参数开始对参数进行编码,并以指定的函数签名作为前缀 abi.encodeWithSignature(string memory sig, …) returns(bytes memory) 相当于abi.encodeWithSelector(bytes4(keccak256(bytes(sig)))) 3.类型成员

函数名 描述 bytes.concat(…) returns(bytes memory) 将多个bytes组合成一个bytes 4.错误处理函数:

函数名 描述 assert(bool condition) 如果条件不满足,则抛出异常,并且恢复原来状态。通常用于内部错误检查 require(bool condition) 如果条件不满足,则执行revert操作。通常用于检查输入或外部组件引起的错误 require(bool condition, string memory message) 同上,并提供了错误信息 revert() 终止执行,并恢复原来状态 revert(string reason) 同上,并提供了原因 需要注意的是,assert异常会消耗掉所有可用的gas,而require从Metropolis版本开始不会小号任何gas。

5.数学和加解密函数:

函数名 描述 addmod(uint x, uint y, uint k) returns(uint) 计算(x + y) % k,其中加法以任意的精度执行,并且当加法结果超过2的256次方也不会被截断 mulmod(uint x, uint y, uint k) returns(uint) 同上,只是加法换成了乘法 keccak(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, byte32 r, bytes32 s) returns(address) 利用椭圆曲线算法恢复公钥地址,失败返回0 6.地址相关函数:

函数名 描述

.balance(uint256) 获取账户地址的余额,单位为wei .code(bytes memory) 返回地址上的代码,可能是空 .codehash(bytes32) 返回地址上的codehash .transfer(uint256 memory) 向指定地址发送一定数量的wei,并且发送2300 gas作为矿工费。如果失败产生一个require异常,则执行revert操作 .send(uint256 amount) returns (bool) 向指定地址发送一定数量的wei,并且发送2300 gas作为矿工费。如果失败返回false .call(bytes memory) returns (bool, bytes memory) 低级调用合约函数,如果成功,则返回true|false和data;可以指定gas数量 .delegatecall(bytes memory) returns (bool, bytes memory) 同上 .staticcall(bytes memory) returns (bool, bytes memory) 同上 7\.合约相关函数:

函数名 描述 this(contract) 将当前合约转换成地址类型 selfdestruct(address payable recipient) 销毁合约,并将余额发送到指定地址上

Fallback function

每个合约都隐式包含一个未命名、没有参数和返回值的函数,成为fallback函数。如果调用合约函数不存在,那么会自动执行fallback函数。除此以外,如果合约接收到以太币,fallback函数也会自动执行,所以fallback函数必须使用payable修饰。

contract SimpleContract {
    
    uint a;
    
    function() external payable {
        a = 100;
    }
    
}

如果想让合约接收以太币,那么就必须提供fallback函数,否则会报错并返还以太币。