Solidity 进阶(下)
合约升级模式
智能合约一旦部署后,默认是不可更改的,但随着业务发展和需求变化,我们可能需要升级合约代码。如何在保持合约不变性的同时实现可升级性呢?
1,不可变性和可升级性的权衡
不可变性的优势
一旦部署,代码不可随意更改,因为任何更改都需要通过新的合约部署实现,并且这一过程是透明的。不可变性确保了数据和逻辑的完整性,防止后门和恶意更改的可能。
可升级性的需求
软件开发本身就是一个迭代的过程,不可能永远不变,随着业务需求的变化和技术进步,必然需要对智能合约进行更新或优化。而且,合约可能存在漏洞,当发现了严重的安全问题时,可升级性可以用来修复漏洞。
如何在不可变性和可升级性之间达成平衡呢?
2. 代理合约模式(Proxy Contracts)
实现智能合约可升级性的一种方式。基本思想是:将合约逻辑(可升级)和合约状态(不可变)分离,用户和一个代理合约交互,这个合约是保持不变的,而这个代理合约调用的后端逻辑合约是可以被替换和升级的。
同时,代理合约需要通过事件和文档清晰地告知用户何时何地进行了升级。
代码示例
contract Proxy {
address implementation;
function setImplementation(address _impl) public {
implementation = _impl;
}
// fallback 函数,用于转发所有的调用到实际合约
function() external payable {
address _impl = implementation;
assembly{
// 将调用数据复制到内存中
calldatacopy(0, 0, calldatasize)
// 使用 delegatecall 调用实际合约
let result := delegatecall(gas, _impl, 0, calldatasize, 0, 0)
// 将返回数据复制到内存中
returndatacopy(0, 0, returndatasize)
// 根据调用结果进行处理: 回滚 or 返回结果
switch result
case 0 { revert(0, returndatasize) }
default { return(0, returndatasize) }
}
}
}
风险
-
中心化控制:合约的升级和修改取决于代理合约的所有者,如果代理合约的所有者行为不当或受到攻击,可能会导致合约的意外修改或停止。
-
复杂性增加:引入代理合约,需要确保代理合约与实际合约之间的交互正确无误,并且需要仔细处理代理合约数据的一致性,这可能增加开发和维护的复杂性。
-
代理合约的引入让合约的逻辑和数据可变,这与区块链的不可逆性原则产生了冲突。虽然可以通过代理合约来保留历史状态和数据,但合约逻辑的变化仍然违背了传统意义上的不可变性。
3. 永久存储模式
把数据存储和业务逻辑分离,数据存储在一个单独的合约中,而逻辑合约则可以被更换。
示例代码
// 数据存储合约
contract Storage {
mapping (address => uint256) public balances;
}
// 业务逻辑合约
contract Logic {
Storage public data;
constructor(address _storageAddress) {
data = Storage(_storageAddress);
}
function deposit() public payable {
data.balances[msg.sender] += msg.value;
}
}
风险
-
成本问题:区块链上的存储空间是有限且昂贵的资源,每一次存储都需要支付相应的费用,而永久存储需要再区块链上保存大量的数据,这可能导致存储成本显著增加。
-
可扩展性问题:随着时间的推移,区块链上的数据量会越来越大,大量的数据存储可能会导致区块链的存储和传输速度减慢,影响整个网络的性能。
-
隐私问题:永久存储的数据通常是公开可见的,任何人都可以访问和查看存储在区块链上的数据,这有可能涉及到隐私和安全的问题。
一个循环字符串的子字符串 s,可以多次链接自身以获得此循环子字符串的最小可能字符串的长度是多少,比如,s=“cabca”,输出 aokution(s)=3。因为 cabca 可以被 cabcabcabcab 自循环链接得到