智能合约代理与批量调用优化:最小代理与MultiCall的应用
最小代理
为每个“代理实例”,用一次固定模板+目标合约地址生成一个转发合约。
他不做逻辑处理,他仅仅是将用户的信息转发到逻辑合约中。
最小代理合约他只是搬运工,他将用户数据传到逻辑合约中来。逻辑合约只做处理,所有数据在代理合约中。就好比将用户swap信息传递到逻辑合约中的swap方法中去,并进行执行。或许你可以理解为,你在remix中部署了一个合约,这个合约中存在很多方法。你不需要没错调用某一个方法而进行重新部署。
所有数据、状态变量、余额,其实都存在代理合约的存储空间内。
为什么?因为:
- 代理使用delegatecall转发,delegatecall的特点是:
- 逻辑用目标合约。
- 存储用自己(代理合约)。
代理合约为什么能支持多用户
因为每个代理合约本身就是一个完整的合约账户:
- 它有独立的以太坊地址;
- 它有自己的ETH余额(可以接收ETH);
- 它的存储槽里可以存放变量(如用户余额、状态标识等)。
逻辑合约与代理合约
即使逻辑是共享的,每个代理依然是“独立的个体”。
- 逻辑合约 = 公司总部,掌管所有业务流程。
- 最小代理合约 = 在各地开设的分公司(省钱建的简易分公司)(业务实例):
- 每家分公司(代理)有自己的账本(数据);
- 但都靠总部(逻辑合约)指挥,按照总部的业务规则运作。
虽然分公司(代理)设施简单,但它们都是独立存在、独立运营的“业务实例”。
工厂合约和最小代理区分
工厂合约和最小代理合约确实都能“批量生成合约”,但它们定位完全不同。
工厂合约是“造合约的工厂”,最小代理合约是“节省成本的产品设计”。
类型 | 工厂合约(Factory) | 最小代理合约(Minimal Proxy) |
本质 | 造合约的工具(部署者) | 一个独立的合约实例(业务壳) |
作用 | 批量生成合约(创建实例) | 作为业务合约接受调用 |
本身是否业务实例 | 否,本身不参与任何业务 | 是,自己就是独立的合约实例 |
部署成本 | 正常合约部署成本 | 极低成本(只是一段代理代码) |
依赖关系 | 工厂可以部署任何类型的合约 | 必须依赖某个逻辑合约 |
功能 | 管理生成过程 | 实际承载业务,负责转发请求处理 |
最大区别就在:
- 最小代理:
- 有独立的以太坊地址;
- 有自己的ETH余额;
- 存储数据在自己身上;
- 用户直接与它交互,它是业务提供者。
- 工厂合约只是创建合约
- 批量部署
- 记录合约地址
- 管理合约
MultiCall原理
简单来说就是:可以一次性抓取大量数据。降低一次一次去抓取需要花费的时间和gas。
MultiCall 只读取,不做写操作
会在很多地方使用到,比如: 假设你有一个钱包地址,想一次性查询它在多个ERC20代币的余额。 MultiCall做法:一次调用MultiCall合约,打包10个balanceOf()
调用,链上统一执行,结果一次返回。
流程:
- 输入:你给合约传入一个查询数组(例如:查询3个不同合约的余额或总供应量)。
- 操作:
- 合约会依次调用每个目标合约,查询它们的指定数据(例如:
balanceOf(address)
或totalSupply()
)。 - 输出:所有查询的结果会一起返回给你,避免多次查询和浪费Gas。
/***Submitted for verification at Etherscan.io on 2019-11-14
*/// hevm: flattened sources of /nix/store/im7ll7dx8gsw2da9k5xwbf8pbjfli2hc-multicall-df1e59d/src/Multicall.sol
pragma solidity >=0.5.0;
pragma experimental ABIEncoderV2;////// /nix/store/im7ll7dx8gsw2da9k5xwbf8pbjfli2hc-multicall-df1e59d/src/Multicall.sol
/* pragma solidity >=0.5.0; */
/* pragma experimental ABIEncoderV2; *//// @title Multicall - Aggregate results from multiple read-only function calls
/// @author Michael Elliot <mike@makerdao.com>
/// @author Joshua Levine <joshua@makerdao.com>
/// @author Nick Johnson <arachnid@notdot.net>contract Multicall {struct Call {address target;bytes callData;}//传入打包的数据,里面有地址和方法function aggregate(Call[] memory calls) public returns (uint256 blockNumber, bytes[] memory returnData) {blockNumber = block.number;returnData = new bytes[](calls.length);for(uint256 i = 0; i < calls.length; i++) {(bool success, bytes memory ret) = calls[i].target.call(calls[i].callData);require(success);returnData[i] = ret;}}// Helper functionsfunction getEthBalance(address addr) public view returns (uint256 balance) {balance = addr.balance;}function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) {blockHash = blockhash(blockNumber);}function getLastBlockHash() public view returns (bytes32 blockHash) {blockHash = blockhash(block.number - 1);}function getCurrentBlockTimestamp() public view returns (uint256 timestamp) {timestamp = block.timestamp;}function getCurrentBlockDifficulty() public view returns (uint256 difficulty) {difficulty = block.difficulty;}function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) {gaslimit = block.gaslimit;}function getCurrentBlockCoinbase() public view returns (address coinbase) {coinbase = block.coinbase;}
}