一、概述
这个合约示例中,我们有以下几个文件:
Types.sol
IProcessor.sol
Processor.sol
Proxy.sol
Client.sol
分别论述下文件的作用:
Types.sol
定义了一些基本类型,与业务逻辑无关;
IProcessor.sol
:关键合约,定义了Processor的接口;
Processor.sol
:关键合约,定义了实际对数据的逻辑操作;
Proxy.sol
:关键合约,定义了数据以及逻辑操作合约Processor
的地址;
Client.sol
定义了用户操作Proxy
合约的方法,免去手动将IProcessor
赋予Proxy
的过程。
1.Types.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
enum Gender {
Male,
Female
}
struct Student {
string name;
Gender gender;
uint256 age;
}
2.IProcessor.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import {Gender} from "./Types.sol";
interface IProcessor {
function setStudentName(string memory) external;
function setStudentGender(Gender) external;
function setStudentAge(uint256) external;
}
3.Processor.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "./IProcessor.sol";
import {Gender, Student} from "./Types.sol";
contract Processor is IProcessor {
Student public student;
address public processor;
function setStudentName(string calldata _name) override external {
student.name = _name;
}
function setStudentGender(Gender _gender) override external {
student.gender = _gender;
}
function setStudentAge(uint256 _age) override external {
student.age = _age;
}
}
4.Proxy.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "./IProcessor.sol";
import {Student} from "./Types.sol";
contract Proxy {
Student public student;
address public processor;
function setProcessor(address _processor) external {
processor = _processor;
}
fallback() external payable {
(bool success, bytes memory data) = processor.delegatecall(msg.data);
require(
success,
"error"
);
}
}
5.Client.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "./IProcessor.sol";
import {Gender, Student} from "./Types.sol";
contract Client {
IProcessor ip;
constructor(address _proxy) {
ip = IProcessor(_proxy);
}
function setName(string calldata _name) external {
ip.setStudentName(_name);
}
function setGender(Gender _gender) external {
ip.setStudentGender(_gender);
}
function setAge(uint256 _age) external {
ip.setStudentAge(_age);
}
}
我们用Client进行操作,比如说,执行setName
函数,输入的参数为字符串Tom
,接下来,就是触发IProcessor
类型的代理合约Proxy
去执行setStudentName
,但事实上,代理合约中并没有setStudentName
,因此会去执行fallback
函数,fallback函数中有关键的delegatecall
函数,它会委托Processor
合约去执行setStudentName
的函数逻辑,但是改变的是Proxy
合约上的student
值。这样就实现了数据与逻辑的分离。下次如果我们需要升级逻辑处理合约就非常简单了——在Proxy
中,使用setProcessor
函数将旧的Processor合约替换成新的Processor合约即可。
二、缺陷
这个实现主要有两个缺陷。
- 其一是
Proxy
中的fallback
函数执行delegatecall
后,有两个返回值,一个是bool类型的success
,一个是bytes类型的data
。这个示例中我们的Processor
合约中的函数没有返回值,倒无所谓,如果有返回值的话,我们其实是需要返回给调用者的。 - 其二是为了使得
delegatecall
的顺利执行,Proxy
和Processor
两个合约保持了相同的变量存储结构,但实际执行时Processor
中的变量却仅仅是摆设,这样的设计就显得累赘,不够优雅。
这两个问题其实都有解决方案,请看下一篇:《Solidity合约代理模式的几个技术技巧》。