Skip to main content

16-1-可升级合约

可升级合约

这一讲,我们介绍了一个简单的可升级合约。它是一个可以改变逻辑合约的代理合约,给不可更改的智能合约增加了升级功能。但是,这个合约有选择器冲突的问题,存在安全隐患。之后我们会介绍解决这一隐患的可升级合约标准:透明代理和UUPS。

如果你理解了代理合约,就很容易理解可升级合约。它就是一个可以更改逻辑合约的代理合约。

1.简单实现

下面我们实现一个简单的可升级合约,它包含3个合约:代理合约,旧的逻辑合约,和新的逻辑合约。

1-1.代理合约

这个代理合约比第46讲中的简单。我们没有在它的fallback()函数中使用内联汇编,而仅仅用了implementation.delegatecall(msg.data);。因此,回调函数没有返回值,但足够教学使用了。

它包含3个变量:

  1. implementation:逻辑合约地址。
  2. admin:admin地址。
  3. words:字符串,可以通过逻辑合约的函数改变。

它包含3个函数

  1. 构造函数:初始化admin和逻辑合约地址。
  2. fallback():回调函数,将调用委托给逻辑合约。
  3. upgrade():升级函数,改变逻辑合约地址,只能由admin调用。
// SPDX-License-Identifier: MIT
// wtf.academy
pragma solidity ^0.8.4;

// 简单的可升级合约,管理员可以通过升级函数更改逻辑合约地址,从而改变合约的逻辑。
// 教学演示用,不要用在生产环境
contract SimpleUpgrade {
address public implementation; // 逻辑合约地址
address public admin; // admin地址
string public words; // 字符串,可以通过逻辑合约的函数改变

// 构造函数,初始化admin和逻辑合约地址
constructor(address _implementation){
admin = msg.sender;
implementation = _implementation;
}

// fallback函数,将调用委托给逻辑合约
fallback() external payable {
(bool success, bytes memory data) = implementation.delegatecall(msg.data);
}

// 升级函数,改变逻辑合约地址,只能由admin调用
function upgrade(address newImplementation) external {
require(msg.sender == admin);
implementation = newImplementation;
}
}

1-2.旧逻辑合约

这个逻辑合约包含3个状态变量,与保持代理合约一致,防止插槽冲突。它只有一个函数foo(),将代理合约中的words的值改为"old"。

// 逻辑合约1
contract Logic1 {
// 状态变量和proxy合约一致,防止插槽冲突
address public implementation;
address public admin;
string public words; // 字符串,可以通过逻辑合约的函数改变

// 改变proxy中状态变量,选择器: 0xc2985578
function foo() public{
words = "old";
}
}

1-3.新逻辑合约

这个逻辑合约包含3个状态变量,与保持代理合约一致,防止插槽冲突。它只有一个函数foo(),将代理合约中的words的值改为"new"。

// 逻辑合约2
contract Logic2 {
// 状态变量和proxy合约一致,防止插槽冲突
address public implementation;
address public admin;
string public words; // 字符串,可以通过逻辑合约的函数改变

// 改变proxy中状态变量,选择器:0xc2985578
function foo() public{
words = "new";
}
}