Solidity 102

函数重载

和C++中的一样:参数返回值不同,函数名相同。

库合约

库合约是一种特殊的合约,为了提升Solidity代码的复用性和减少gas而存在。库合约是一系列的函数合集。

与普通合约的区别

  • 不能存在状态变量。
  • 不能够继承或被继承。
  • 不能接收以太币。
  • 不可以被销毁。

需要注意的是,库合约中的函数可见性如果被设置为public或者external,则在调用函数时会触发一次delegatecall。而如果被设置为internal,则不会引起。对于设置为private可见性的函数来说,其仅能在库合约中可见,在其他合约中不可用。

使用

// 方式1:
// 利用using for指令
using Strings for uint256;
function getString1(uint256 _number) public pure returns(string memory){
    // 库合约中的函数会自动添加为uint256型变量的成员
    return _number.toHexString();
}


// 方式2:
// 直接通过库合约名调用
function getString2(uint256 _number) public pure returns(string memory){
    return Strings.toHexString(_number);
}

Import

目录结构

文件结构
├── Import.sol
└── Yeye.sol

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

直接饮用网上的合约的全局地址

// 通过网址引用
import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol';

通过npm的目录导入,例子:

import '@openzeppelin/contracts/access/Ownable.sol';

通过指定全局符号导入合约特定的全局符号,例子:

import {Yeye} from './Yeye.sol';

接收ETH

Solidity 有两种特殊的回调函数,receive() 和 fallback(), 在以下情况被使用:

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

这里主要讲接收ETH的情况

receive()

合约收到ETH转账的时候被调用,一个合约最多有一个receive函数,声明方式不需要function,不需要参数和返回值,且必须包含external payable

receive() external payable { ... }

fallback

fallback()函数会在调用合约不存在的函数时被触发。可用于接收ETH,也可以用于代理合约proxy contract。

fallback() external payable { ... }

两者区别

两者触发的规则如下:

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

发送ETH

有三种方法向其他合约发送ETH,他们是:transfer(),send()和call(),其中call()是被鼓励的用法。

先部署一个接收ETH的合约:

contract ReceiveETH {
    // 收到eth事件,记录amount和gas
    event Log(uint amount, uint gas);
    
    // receive方法,接收eth时被触发
    receive() external payable{
        emit Log(msg.value, gasleft());
    }
    
    // 返回合约ETH余额
    function getBalance() view public returns(uint) {
        return address(this).balance;
    }
}

发送 ETH 合约

首先,先在发送ETH合约SendETH中实现payable的构造函数和receive(),让我们能够在部署时和部署后向合约转账

contract SendETH {
    // 构造函数,payable使得部署的时候可以转eth进去
    constructor() payable{}
    // receive方法,接收eth时被触发
    receive() external payable{}
}

transfer

用法:

接收方地址.transfer(发送ETH数额)
  • transfer()的gas限制是2300,足够用于转账,但对方合约的fallback()receive()函数不能实现太复杂的逻辑。

  • transfer()如果转账失败,会自动revert(回滚交易)。

// 用transfer()发送ETH
function transferETH(address payable _to, uint256 amount) external payable{
    _to.transfer(amount);
}

部署两个合约之后点击transferETH输入ReceiveETH合约的地址和数额,就可以看到发送成功

send

用法:

接收方地址.send(发送ETH数额)
  • send()的gas限制是2300,足够用于转账,但对方合约的fallback()receive()函数不能实现太复杂的逻辑。

  • send()如果转账失败,不会revert(回滚交易)。

  • send()的返回值是bool,代表着转账成功或失败,需要额外代码处理一下

error SendFailed(); // 用send发送ETH失败error

// send()发送ETH
function sendETH(address payable _to, uint256 amount) external payable{
    // 处理下send的返回值,如果失败,revert交易并发送error
    bool success = _to.send(amount);
    if(!success){
        revert SendFailed();
    }
}

call

用法是:

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

// call()发送ETH
function callETH(address payable _to, uint256 amount) external payable{
    // 处理下call的返回值,如果失败,revert交易并发送error
    (bool success,) = _to.call{value: amount}("");
    if(!success){
        revert CallFailed();
    }
}

调用其他合约

在Solidity中,一个合约可以调用另一个合约的函数,这在构建复杂的DApps时非常有用。

先写一个合约,用于被其他合约调用

contract OtherContract {
    uint256 private _x = 0; // 状态变量_x
    // 收到eth的事件,记录amount和gas
    event Log(uint amount, uint gas);
    
    // 返回合约ETH余额
    function getBalance() view public returns(uint) {
        return address(this).balance;
    }

    // 可以调整状态变量_x的函数,并且可以往合约转ETH (payable)
    function setX(uint256 x) external payable{
        _x = x;
        // 如果转入ETH,则释放Log事件
        if(msg.value > 0){
            emit Log(msg.value, gasleft());
        }
    }

    // 读取_x
    function getX() external view returns(uint x){
        x = _x;
    }
}

传入合约地址

可以利用合约的地址和合约代码(或接口)来创建合约的引用:_Name(_Address),其中_Name是合约名,应与合约代码(或接口)中标注的合约名保持一致,_Address是合约地址。然后用合约的引用来调用它的函数:_Name(_Address).f(),其中f()是要调用的函数。

contract CallContract{
    function callSetX(address _Address, uint256 x) external{
        OtherContract(_Address).setX(x);
    }
}

传入合约变量

contract CallContract{
    function callGetX(OtherContract _Address) external view returns(uint x){
        x = _Address.getX();
    }
}

创建合约变量

contract CallContract{
    function callGetX2(address _Address) external view returns(uint x){
        OtherContract oc = OtherContract(_Address);
        x = oc.getX();
    }
}

调用合约并发送ETH

如果目标合约的函数是payable的,那么我们可以通过调用它来给合约转账:_Name(_Address).f{value: _Value}(),其中_Name是合约名,_Address是合约地址,f是目标函数名,_Value是要转的ETH数额(以wei为单位)。

OtherContract合约的setX函数是payable的,在下面这个例子中我们通过调用setX来往目标合约转账。

contract CallContract{
    function setXTransferETH(address otherContract, uint256 x) payable external{
        OtherContract(otherContract).setX{value: msg.value}(x);
    }

}

Call

call不仅可以发送ETH,还可以调用合约

我先说一下我的情况: 我现在从事网络安全的工作,但工作之余我想学习web3相关的东西,目前已经知道的东西:BTC的钱包的原理,包括私钥公钥生成这种,直接我都写代码实现过;目前知道solidity的基础语法,除此之外,关于web3的其他就都不知道了,主要是知道的一些其他的东西都是碎片化、孤岛化的,并没有系统的串联起来,给我一些具体的学习路线,我知道web3东西很多,但给我一些具体的学习路线