在我们基于智能合约做应用的时候,很多时候需要创建同一个合约的很多份实例。这里有个更省 gas 费的做法。
在我们基于智能合约做应用的时候,很多时候需要创建同一个合约的很多份实例。比如在做 DeFi 借贷应用时,有可能需要对每个借贷单生成一个借贷合约实例,在做多签钱包时,当用户要创建一个新的多签钱包时,合约层相应的会创建一个新的多签合约实例。
这种情况下,我们往往会引入一个工厂合约,通过特定的参数来创建对应的合约实例。就像下面的多签合约工厂一样。
pragma solidity ^0.4.15;
import "./Factory.sol";
import "./MultiSigWallet.sol";
contract MultiSigWalletFactory is Factory {
/*
* Public functions
*/
/// @dev Allows verified creation of multisignature wallet.
/// @param _owners List of initial owners.
/// @param _required Number of required confirmations.
/// @return Returns wallet address.
function create(address[] _owners, uint _required)
public
returns (address wallet)
{
wallet = new MultiSigWallet(_owners, _required);
register(wallet);
}
}
这有个什么毛病呢?就是我们每创建一个新的合约实例,会把几乎一模一样的合约字节码也完全拷贝一份,放到新生成的合约地址所对应的代码存储空间里面。这样就造成了大量的合约代码在存储上不必要的冗余。
在这种情况下,有没有可能在保留必要状态的同时,重用已有代码的逻辑呢? 像下面这个图所展示的这样,我们通过代理合约去访问真正的业务逻辑实现合约,需要不同合约实例的时候我们创建代理合约实例就可以了,代码量比较大的合约实现保持一份不动。这样其实就可以节省不少空间了,节省空间就是节省 gas 费啊。
EIP1167 其实就是这种代理合约的更底层支持,让通过代理来创建新合约实例所花费的 gas 更少。
EIP1167 所做的事情可以概括如下,它只是把这个步骤翻译成了字节码形式
我们可以 clone 工厂去动态创建代理合约并返回新合约的地址
contract CloneFactory {
function createClone(address target) internal returns (address result) {
bytes20 targetBytes = bytes20(target);
assembly {
let clone := mload(0x40)
mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
mstore(add(clone, 0x14), targetBytes)
mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
result := create(0, clone, 0x37)
}
}
}
可以进一步封装一下,比如:
contract WalletFactory is CloneFactory {
address Template = 0x692a70D2e424a56D2C6C27aA97D1a86395877b3A;
function createWallet() external returns (address newWallet) {
newWallet = createClone(Template);
}
}
// constructor
constructor(address _owner) external {
owner = _owner;
}
// initializer
function setOwner(address _owner) external {
require(owner == address(0));
owner = _owner;
}
contract WalletFactory is CloneFactory {
address Template = 0x692a70D2e424a56D2C6C27aA97D1a86395877b3A;
function createWallet(address _owner) external returns (address newWallet) {
newWallet = createClone(Template);
newWallet.setOwner(_owner);
}
}
// 使用 setter 方法
mapping(address => bool) public isOwner;
uint public dailyWithdrawLimit;
uint public signaturesRequired;
function set(address[] _owner, uint limit, uint required) external {
require(dailyWithdrawLimit == 0 && signaturesRequired == 0);
dailyWithdrawLimit = limit;
signaturesRequired = required;
//DO SOMETHING ELSE
}
// 使用常量
string public constant name = "DemoToken";
string public constant symbol = "DET";
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!