透明代理合约
本章讲解如何实现透明代理合约。
在智能合约编译成字节码的过程中,合约中定义的各个函数,在内部实际上都是通过函数签名来表示的。这个函数签名是一个 4 字节长度的序列,由函数名称和参数类型组合的哈希值截取前 4 个字节位组成。由于函数签名的长度比较短,所以不同的函数有时可能会生成相同的签名,造成“函数签名冲突”。
关于函数签名的生成规则等相关内容,可以参考高级教程中的函数选择器章节。
在使用 Solidity 编写的单个智能合约内部,如果两个不同的函数声明产生了相同的函数签名,这将导致合约无法顺利编译。这是因为合约内部无法区分这两个功能上不同但签名相同的函数。
我们看以下合约:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SelectorCollision {
function burn(uint256) external pure{}
function collate_propagate_storage(bytes16) external pure{}
}
两个不同的函数定义 burn 和 collate_propagate_storage 就具有相同的函数签名。
我们将这个合约复制到 Remix 进行编译,就会报错:
TypeError: Function signature hash collision for collate_propagate_storage(bytes16)
在这种情况下,我们就必须调整函数的声明,确保每个函数都有一个独一无二的签名。
另一方面,如果这种函数签名的冲突出现在两个不同的合约之间,那么这并不会构成问题。因为这两个合约是分开的实体,它们的内部函数签名冲突不会影响彼此的正常运行和编译。
1. 代理合约的函数签名冲突
在代理合约的模式下,虽然代理合约和逻辑合约是两个独立的合约,但是如果在这两个合约中出现函数签名冲突,那么就会导致非常严重的问题。
我们举个例子来说明这种情况:
contract Porxy {
function foo() external pure{}
fallback external {
//......
}
}
contract Logic {
function foo() external pure{}
}
代理合约 Porxy 和逻辑合约 Logic 中,有一个完全相同的函数声明 foo。
当调用者想要调用逻辑合约 Logic 中的 foo 函数时,可以通过代理合约调用:
proxy.call("0xc2985578")
但这条语句的实际效果却是调用了代理合约中的函数 foo,而不会调用逻辑合约中的 foo 函数,因为两者的函数签名相同。
所以,函数签名冲突在代理合约的模式下,将会引发严重的业务逻辑和安全问题。
即使我们在编程的时候非常小心,不会出现函数声明完全相同的情况。但是,不同的函数声明,也会出现函数签名相同的情况。
比如下面的合约:
contract Porxy {
function burn(uint256) external pure{}
fallback external {
//......
}
}
contract Logic {
function collate_propagate_storage(bytes16) external pure{}
}
其中,burn 和 collate_propagate_storage 的函数签名都是 0x42966c68。
函数签名冲突问题很难避免,而且导致的后果不可预料。所以,我们必须找到一个更好的方案加以解决。
2. 透明代理
为了解决代理模式下的函数签名冲突问题,于是就产生了透明代理(Transparent Proxy)的解决方案。
透明代理指的是一种代理合约设计,它能够区分普通用户和管理员(或所有者)之间的交互,并对他们采取不同的行为。
透明代理的工作原理:
透明代理合约能够通过 msg.sender 识别发起交易的是普通用户还是管理员。
如果是管理员,直接调用代理合约中的函数,不会通过 fallback 转发交易给逻辑合约。
如果是普通用户,代理合约将这些调用委托给逻辑合约,就好像这些调用是直接发送给逻辑合约一样。