前言
在【关于可变合约的二三事】相关的文章中,我们讨论了代理合约的使用方式,但文中的最后,也提到了,给出的代理合约例子是有问题的,不能用在真实项目中,哪真实项目中是怎么使用代理合约的呢?本文就以最近比较火热的项目TwitterScan作为分析对象,讨论其使用代理合约的方式。
在讨论前,有必要提一下,网上很多代理合约的文章都脱离了真实项目,即你了解完后,依旧看不懂真实项目的合约,比较难受。最好的学习方式还是专业机构的文档+真实在跑的项目,这样才能验证你自己的理解是否正确。
TwitterScan简介
因为有些朋友可能不知道TwitterScan是什么项目,这里简单介绍一下。
TwitterScan官网:https://twitterscan.com/
一个Web3数据分析工具,可以追踪链上数据(即各种Token和链的交易数据)还可以追踪Twitter Web3圈KOL社交发言等链下数据。
嗯,这些都不是重点,重点是,这个项目在短短几个月内,获利536.68个ETH(即获利近500w RMB),而且,技术难度也不会太高。
这里,插一下我目前的一个观点:web3工具类的项目,开发难度不会特别大,但起来的项目收益都很高,所以开发者同行们,可以多多关注一下这个领域的工具。
简单代理合约的问题
基于【关于可变合约的二三事】一文,我们知道代理合约会通过delegatecall函数来实现委托调用,其目的是将项目数据放在代理合约中,将项目玩法逻辑放在逻辑合约中,当项目需要更新时,直接替换代理合约关联的逻辑合约,便可以无痛切换成新的玩法且数据不会丢失。当然,逻辑合约出现了bug也可以利用这种方式修改一下。
那代理合约出现bug了呢?抱歉,救不了,要救只能通过社交更新法,即告诉大家,旧的合约有问题,一起来玩新的合约吧,我们官方不承认旧合约里的数据了,嗯,这就是所谓的社交更新,你要让大家配合你玩。
为了稳妥起见,多数项目都不会自己去开发代理合约,而是直接使用专业机构提供的代理合约模型,比如openzeppelin提供了不同模式的代理合约玩法,TwitterScan项目便使用openzeppelin提供的透明代理合约。
在看TwitterScan的合约前,有必要讨论一下简单代理合约的问题。
slot clash
solidity语言有个比较隐晦的特性,那便是slot的分布,很多人看solidity的语法与JavaScript类似,就模仿JavaScript的形式写代码,从而在这里踩坑。
为了方便你理解,这里写给出一个简单的Proxy合约和逻辑合约:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
contract Proxy {
address public implementation;
address public admin;
uint256 public x = 0;
constructor() {
admin = msg.sender;
}
modifier onlyAdmin {
require(msg.sender == admin);
_;
}
function setImplementation(address _implementation) external onlyAdmin{
implementation = _implementation;
}
fallback() external payable {
_delegate();
}
receive() external payable {
_delegate();
}
function _delegate() internal {
assembly {
let _implementation := sload(0)
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), _implementation, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
}
contract LogicsContract {
// 与代理合约中变量的顺序要一致
address public implementation;
address public admin;
uint256 public x = 0;
function add_x(uint256 _x) external returns(uint256) {
x += _x;
return x;
}
}
上面的代码是从【关于可变合约的二三事】中直接复制过来的,看到LogicsContract中的注释,solidity要求你,逻辑合约要与代理合约的变量顺序是一直的,究其原因,便是slot。
在solidity中,每个合约中的变量都是按顺序放置的,比如LogicsContract合约中的implementation变量在第0个slot,admin变量则在第1个slot,它刚好与proxy合约中的implementation变量对应上,如果你将顺序调整一下,比如调整成如下形式:
contract LogicsContract {
// x => 0 slot
uint256 public x = 0;
// implementation => 1 slot
address public implementation;
// admin => 2 slot
address public admin;
function add_x(uint256 _x) external returns(uint256) {
x += _x;
return x;
}
}
如果调整成上面的顺序,那么x变量就在第0个slot了,而proxy中,第0个slot是implementation变量,这样当我们调用add_x函数时,在LogicsContract中,处理的是第0个slot,即x变量,但映射到Proxy中,则变成改变的是i