16. Solidity:在合約中創(chuàng)建合約、合約自毀

16.1 在合約中創(chuàng)建合約

16.1.1 create

create的用法很簡單,就是new一個合約,并傳入新合約構(gòu)造函數(shù)所需的參數(shù):

Contract x = new Contract{value: _value}(params)

其中Contract是要創(chuàng)建的合約名,x是合約對象(地址),如果構(gòu)造函數(shù)是payable,可以創(chuàng)建時轉(zhuǎn)入_value數(shù)量的ETH,params是新合約構(gòu)造函數(shù)的參數(shù)。

例子:極簡Uniswap
Uniswap V2核心合約中包括兩個合約:

  1. UniswapV2Pair:幣對合約,用于管理幣對地址、流動性、買賣。
  2. UniswapV2Factory:工廠合約,用于創(chuàng)建新的幣對合約,并管理幣對地址。

本節(jié)通過create方法實現(xiàn)一個極簡Uniswap:

  1. Pair合約管理幣對地址。
  2. PairFactory工廠合約創(chuàng)建新的幣對合約,并管理幣對地址。

Pair合約

contract Pair {
    address public factory;     // 工廠合約地址
    address public token0;      // token0合約地址
    address public token1;      // token1合約地址

    // 初始化factory
    constructor() payable {
        factory = msg.sender;
    }

    // 初始化token0、token1
    function initialize(address _token0, address _token1) external {
        require(msg.sender == factory, "Fobidden");
        token0 = _token0;
        token1 = _token1;
    }
}

PairFactory合約:

contract PairFactory {
    mapping (address => mapping (address => address)) public getPair;       // 通過兩個代幣地址獲取幣對合約地址
    mapping (address => mapping (address => bool)) public pairExist;       // 通過兩個代幣地址判斷幣對合約是否存在

    address[] public allPairs;      // 保存所有幣對合約地址

    function createPair(address _token0, address _token1) external returns (address pairAddr) {
        // 檢查
        require(pairExist[_token0][_token1] == false && pairExist[_token1][_token0] == false, "pair exist!");
        // 創(chuàng)建幣對合約
        Pair pair = new Pair();
        // 初始化token0、token1
        pair.initialize(_token0, _token1);

        pairAddr = address(pair);
        // 更新map
        getPair[_token0][_token1] = pairAddr;
        getPair[_token1][_token0] = pairAddr;
        pairExist[_token0][_token1] = true;
        pairExist[_token1][_token0] = true;
        // 更新數(shù)組
        allPairs.push(pairAddr);
    }
}

16.1.2 create2

CREATE2 操作碼使我們在智能合約部署在以太坊網(wǎng)絡(luò)之前就能預(yù)測合約的地址。Uniswap創(chuàng)建Pair合約用的就是CREATE2而不是CREATE。

CREATE如何計算地址:
智能合約可以由其他合約和普通賬戶利用CREATE操作碼創(chuàng)建。 在這兩種情況下,新合約的地址都以相同的方式計算:創(chuàng)建者的地址(通常為部署的錢包地址或者合約地址)和nonce(該地址發(fā)送交易的總數(shù),對于合約賬戶是創(chuàng)建的合約總數(shù),每創(chuàng)建一個合約nonce+1))的哈希。

新地址 = hash(創(chuàng)建者地址, nonce)

創(chuàng)建者地址不會變,但nonce可能會隨時間而改變,因此用CREATE創(chuàng)建的合約地址不好預(yù)測。

CREATE2如何計算地址:
CREATE2的目的是為了讓合約地址獨立于未來的事件。不管未來區(qū)塊鏈上發(fā)生了什么,你都可以把合約部署在事先計算好的地址上。用CREATE2創(chuàng)建的合約地址由4個部分決定:

  • 0xFF:一個常數(shù),避免和CREATE沖突
  • 創(chuàng)建者地址
  • salt(鹽):一個創(chuàng)建者給定的數(shù)值
  • 待部署合約的字節(jié)碼(bytecode)

新地址 = hash("0xFF",創(chuàng)建者地址, salt, bytecode)

如果創(chuàng)建者使用 CREATE2 和提供的 salt 部署給定的合約bytecode,它將存儲在新地址中,而新地址是可以提前計算的。
create2方法:Pair合約

contract Pair {
    address public factory;
    address public token0;
    address public token1;

    constructor(address _token0, address _token1) payable {
        factory = msg.sender;
        token0 = _token0;
        token1 = _token1;
    }
}

create2方法:PairFactory合約:

contract PairFactory {
    mapping (address => mapping (address => address)) public  getPair;
    address[] public allPairs;


    function create2Pair(address _token0, address _token1) external returns (address pairAddr) {
        // 要求兩個代幣不相同
        require(_token0 != _token1, "IDENTICAL_ADDRESSES");

        // token地址排序
        (address tokenA, address tokenB) = _token0 < _token1 ? (_token0, _token1) : (_token1, _token0);
        // 根據(jù)兩個代幣地址計算鹽
        bytes32 _salt = keccak256(abi.encodePacked(tokenA, tokenB));

        // 創(chuàng)建幣對合約
        Pair pair = new Pair{salt:_salt}(tokenA, tokenB);

        pairAddr = address(pair);
        // 更新map
        getPair[tokenA][tokenB] = pairAddr;
        getPair[tokenB][tokenA] = pairAddr;

        // 更新數(shù)組
        allPairs.push(pairAddr);
    }

    function calculateAddr(address _token0, address _token1) external view returns (address) {
        // 要求兩個代幣不相同
        require(_token0 != _token1, "IDENTICAL_ADDRESSES");

        // token地址排序
        (address tokenA, address tokenB) = _token0 < _token1 ? (_token0, _token1) : (_token1, _token0);
        // 根據(jù)兩個代幣地址計算鹽
        bytes32 _salt = keccak256(abi.encodePacked(tokenA, tokenB));

        address pridictAddr = address(uint160(uint(keccak256(abi.encodePacked(
            bytes1(0xff),
            address(this),
            _salt,
            keccak256(abi.encodePacked(
                type(Pair).creationCode,
                abi.encode(tokenA,tokenB)
            ))
        )))));
        return pridictAddr;
    }
}

需要注意的是,合約字節(jié)碼需要包括參數(shù)tokenA和tokenB,并且參數(shù)需要使用abi.encode進(jìn)行編碼:

abi.encode(tokenA,tokenB)

create2的優(yōu)點:

  • 可以在鏈下計算出已經(jīng)創(chuàng)建的交易池的地址
  • 其他合約不必通過接口來查詢交易池的地址,可以節(jié)省 gas
  • 合約地址不會因為reorg (區(qū)塊重組、分叉) 而改變
  • 如果一個合約自毀了,那么新合約未來可以再次部署到這個地址上
  • 在未部署前可以提前獲取合約地址

16.2 合約自毀

selfdestruct命令可以用來刪除智能合約,并將該合約剩余ETH轉(zhuǎn)到指定地址。selfdestruct是為了應(yīng)對合約出錯的極端情況而設(shè)計的。它最早被命名為suicide(自殺),但是這個詞太敏感。為了保護(hù)抑郁的程序員,改名為selfdestruct。

selfdestruct使用起來非常簡單:

selfdestruct(_addr);

其中_addr是接收合約中剩余ETH的地址。
示例:

contract Kill {
    constructor() payable {}

    function kill() external {
        selfdestruct(payable(msg.sender));
    }

    function getBalance() external view returns (uint) {
        return address(this).balance;
    }
}
  1. 合約部署時,轉(zhuǎn)入10ETH,調(diào)用getBalance ()可以獲取到合約余額為10ETH,同時錢包地址余額減少了10ETH;
  2. 調(diào)用合約的kill()方法,錢包余額增加了10ETH,同時再次調(diào)用getBalance ()函數(shù)會報錯:
{
  "error": "Failed to decode output: Error: hex data is odd-length (argument=\"value\", value=\"0x0\", code=INVALID_ARGUMENT, version=bytes/5.7.0)"
}

注意:

  1. 對外提供合約銷毀接口時,最好設(shè)置為只有合約所有者可以調(diào)用,可以使用函數(shù)修飾符onlyOwner進(jìn)行函數(shù)聲明。
  2. 當(dāng)合約被銷毀后與智能合約的交互會報錯。
  3. 當(dāng)合約中有selfdestruct功能時常常會帶來安全問題和信任問題,合約中的Selfdestruct功能會為攻擊者打開攻擊向量(例如使用selfdestruct向一個合約頻繁轉(zhuǎn)入token進(jìn)行攻擊,這將大大節(jié)省了GAS的費用,雖然很少人這么做),此外,此功能還會降低用戶對合約的信心。
  4. selfdestruct 已被認(rèn)為是廢棄的(EIP-6049),編譯器將在 Solidity 和 Yul 中警告其使用,包括內(nèi)聯(lián)程序集。目前沒有替代方案,但強烈不建議使用,因為它最終將改變語義,并且所有使用它的合約將在某些方面受到影響。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容