怎么样实现可升级的智能合约

当前位置:首页 > 币圈百科 > 怎么样实现可升级的智能合约

怎么样实现可升级的智能合约

2022-11-08币圈百科218

智能合约的重要性变得越来越明显。如今,整个加密货币生态系统都是由智能合约驱动的!无论我们多么小心,或者我们的代码测试工作做得多么好,如果我们创建了一个复杂的系统,那么我们就有必要更新契约逻辑,以修复其现有的漏洞或添加必要的缺失功能。有时,由于EVM虚拟机的更改或新发现的漏洞,我们可能需要升级我们的智能合同。

一般来说,开发商可以很容易地升级他们的软件,但区块链的情况不同,因为他们有很难改变的属性。如果我们部署一个合同,就像倒水一样。但是,如果我们使用适当的技术,我们可以在不同的地址部署新的合同,并使旧的合同无效。以下是创建可伸缩智能合约的一些最常见的方法。

rt

主从契约

x】

主从技术是可以实现智能契约升级的最基本也最容易理解的技术之一。在这种技术中,我们部署一个主契约和其他契约,其中主契约负责存储所有其他契约的地址,并在需要时返回所需的地址。当这些契约需要与其他契约进行通信时,它们将充当从契约,从主契约中获取其他契约的最新地址。为了升级智能合约,我们只需将其部署在网络上,并更改主合约中的地址。尽管这远不是开发可伸缩智能合约的最佳方式,但却是最简单的。这种方法有许多限制,其中之一是我们不能轻易地将契约的数据或资产迁移到新的契约中。

永久存储契约

在这项技术中,我们将逻辑契约与数据契约分开。该合同应该是永久性的,不可升级。逻辑协定可以根据需要升级任意多次,并且更改将被通知给数据协定。这是一个相当基础的技术,有一个明显的缺陷。因为数据契约是不可升级的,数据结构的任何改变或者数据契约的漏洞都会使所有数据变得无用。这种技术的另一个问题是,如果一个逻辑契约想要访问/操作区块链上的数据,那么这个逻辑契约将需要进行外部调用,并且外部调用将消耗额外的气体。通常这种技术会结合主从技术来促进契约之间的通信。

可升级存储代理契约

我们可以通过让永久存储契约充当逻辑契约的代理来防止支付额外的气费。该代理契约和该逻辑契约将继承相同的存储契约,因此它们的存储将在EVM虚拟机中对齐。这个代理契约会有一个回退功能,这个回退功能会委托调用这个逻辑契约,然后这个逻辑契约可以在代理存储中更改。这份代理合同将是永久的。这节省了多次调用存储契约所需的gas,并且无论对数据进行多少次更改,都只需要一次委托调用。

该技术有三个组成部分:

代理契约:它将作为永久存储,负责委托和调用逻辑契约;逻辑契约:负责处理所有数据;存储结构:包含存储结构,会被代理契约和逻辑契约继承,使它们的存储指针在区块链上保持同步;p1

委托调用

这项技术的核心在于EVM提供的DELEGATECALL操作码,它就像一个普通的调用操作码,只不过目标地址的代码是在调用契约的上下文中执行的,而原始调用的msg.sender和msg.value会被保留。简单地说,DELEGATECALL基本上允许目标契约在调用契约的存储中为所欲为。

我们将利用这一点,创建一个代理契约,它将使用DELEGATECALL操作码来委托逻辑契约的调用,这样我们就可以在代理契约中保持数据安全,同时可以自由地更改逻辑契约。

如何使用可扩展存储代理合同?

我们来深究一下细节。我们需要的第一个合同是存储结构。它将定义我们需要的所有存储变量,并将由代理契约和执行契约继承。看起来会是这样的:

契约存储结构{地址公共实现;公共所有者地址;映射(address=uint)内部点;uint内部totalPlayers}

我们现在需要一个执行/逻辑契约。让我们创建一个简单版本的合同,在添加新玩家时不会增加totalPlayers计数器。

contract ImplementationV1是storage structure { modifier only owner(){ require(msg . sender==owner);_;}函数addPlayer(address _player,uint _ points)public only owner { require(points[_ player]==0);points[_ player]=_ points;}函数设定点(address _player,uint _ points)public only owner { require(points[_ player]!=0);points[_ player]=_ points;}}

这里是最关键的部分:代理合同;

合同代理是storage structure { modifier only owner(){ require(msg . sender==owner);_;}/* * * * @设置所有者地址的dev构造函数*/constructor()public { owner=msg . sender;}/* * * * @ dev升级新实现的实现地址* @param _newImplementation地址*/function upgrade to(address _ new implementation)external only owner { require(implementation!=_ new implementation);_ set implementation(_ new implementation);}/* * * * @ dev回退函数允许对给定的实现执行delegatecall*。此函数将返回*无论实现调用返回什么*/function()payable public { address impl=implementation;要求(impl!=地址(0));assembly { let ptr:=m load(0x 40)call data copy(ptr,0,calldatasize)let result:=delegate call(gas,impl,ptr,calldatasize,0,0)let size:=returndatasize returndatacopy(ptr,0,size)switch result case 0 { revert(ptr,size) }default { return(ptr,size)} } }/* * * * @ dev设置当前实现的地址* @param _newImp新实现的地址*/function _ set implementation(address _ 1}}

为了让契约生效,我们首先需要部署代理契约和实现V1契约,然后调用这个代理契约的upgradeTo(address)函数,同时将我们的实现V1契约地址pass掉。现在,我们可以忘记执行V1合同的地址,而将代理合同的地址作为我们的主要地址。

为了升级这个契约,我们需要创建一个新的逻辑契约实现,可以是这样的:

契约实现v2是实现v1 { function add player(address _ player,uint _ points)public only owner { require(points[_ player]==0);points[_ player]=_ points;玩家总数;}}

您应该注意,此合同也继承了存储结构合同,尽管它是间接的。

所有的实现方案都必须继承这个存储结构契约,代理契约部署后不得更改,以免意外覆盖代理的存储。

为了实现升级,我们在网络上部署这个契约,然后调用代理契约的upgradeTo(address)函数,同时把ImplementationV2契约的地址传过去。

这项技术使得升级契约逻辑变得相当容易,但是它仍然不允许我们升级契约的存储结构。我们可以用非结构化的代理契约来解决这个问题。

非结构化可扩展存储代理合同

这是升级智能合同的最高级方法之一。它将契约的地址和固定位置的所有者保存在存储中,这样它们就不会被执行/逻辑契约提供的数据覆盖。 我们可以使用sload以及商店操作码来直接读取和写入由固定指针所引用的特定存储槽

此方法利用了存储中状态变量的布局,以避免逻辑合约覆盖掉固定位置。如果我们将固定位置设置为0x7,那么在使用前七个存储槽后,它就会被覆盖掉。为了避免这种情况,我们将固定位置设置为类似ke ccak 256(" org。政府街区。实施。地址”).

这消除了在代理合约中继承存储结构合约的需要,这意味着我们现在也可以升级存储结构了。然而,升级存储结构是一项棘手的任务,因为我们需要确保,我们所提交的更改,不会导致新的存储布局与先前的存储布局不匹配

这项技术有两个组成部分

1,代理合约:它负责将执行合约的地址存储在一个固定的地址当中,并负责委托调用它;2、执行合约:它是主要合约,负责把我逻辑以及存储结构;

你甚至可以将这项技术用于你现有的合约,因为它不需要对你的执行合约进行任何更改

这个代理合约会是这样子的:

合同非结构化代理{//当前实施字节32私有常量实施位置=keccak 256(' org。政府街区。实施。地址’);合同字节32私有常量proxyOwnerPosition的所有者的存储位置=keccak 256(' org。政府街区。代理。所有者’);/* * * * @如果被所有者之外的任何帐户调用,开发将抛出/modifier only代理所有者(){ require(msg。sender==代理所有者());_;}/* * * * @ dev构造函数设置owner */constructor()public { _ setUpgradeabilityOwner(msg。发件人);}/* * * * @ dev允许当前所有者转让所有权* @param _newOwner转让所有权的地址*/function transferProxyOwnership(address _ new owner)public only代理所有者{ require(_ new owner!=地址(0));_ setUpgradeabilityOwner(_新所有者);}/* * * * @ dev允许代理所有者升级新实现的实现* @ param _ implementation地址*/函数升级到(address _ implementation)public only代理所有者{ _ upgrade到(_ implementation);}/* * * * @ dev告知当前实现的地址* @当前实现的返回地址*/函数实现()公共视图返回(地址impl){ bytes 32 position=实现位置;assembly { impl:=sload(position)} }/* * * * @ dev告知拥有者的地址* @返回拥有者的地址*/函数代理所有者()公共视图返回(地址所有者){ bytes 32 position=proxyOwnerPosition;assembly {owner := sload(position)}}/*** @dev Sets the address of the current implementation* @param _newImplementation address of the new implementation*/function _setImplementation(address _newImplementation)internal{bytes32 position = implementationPosition;assembly {sstore(position, _newImplementation)}}/*** @dev Upgrades the implementation address* @param _newImplementation address of the new implementation*/function _upgradeTo(address _newImplementation) internal {address currentImplementation = implementation();require(currentImplementation != _newImplementation);_setImplementation(_newImplementation);}/*** @dev Sets the address of the owner*/function _setUpgradeabilityOwner(address _newProxyOwner)internal{bytes32 position = proxyOwnerPosition;assembly {sstore(position, _newProxyOwner)}}}

怎么样使用非结构化可升级存储代理合约?

使用非结构化可升级存储代理合约是非常简单的,因为这种技术几乎可以处理所有现有的合约。想要使用这种技术,你只需要遵循以下步骤:

部署代理合约和执行合约;调用代理合约的upgradeTo(address)函数,同时pass掉执行合约的地址。

我们现在可以忘掉这个执行合约地址,然后把代理合约的地址作为主地址。

而要升级这个新实施的合约,我们只需要部署新的执行合约,并调用代理合约的upgradeTo(address) 函数,同时pass掉这个新执行合约的地址。就是这么简单!

让我们简单举个例子。我们将再次使用上述可升级存储代理合约中使用的同一逻辑合约,但是我们不需要用到存储结构。因此,我们的ImplementationV1合约看起来会是这样的:

contract ImplementationV1 {address public owner;mapping (address => uint) internal points;modifier onlyOwner() {require (msg.sender == owner);_;}function initOwner() external {require (owner == address(0));owner = msg.sender;}function addPlayer(address _player, uint _points)public onlyOwner{require (points[_player] == 0);points[_player] = _points;}function setPoints(address _player, uint _points)public onlyOwner{require (points[_player] != 0);points[_player] = _points;}}

下一步是部署这个执行合约以及我们的代理合约。然后,再调用代理合约的upgradeTo(address) 函数,同时pass掉执行合约的地址。

你可能注意到,在这蓑衣网小编2022个执行合约中,甚至没有声明totalPlayers变量,我们可以升级这个执行合约,其中具有 totalPlayers变量,这个新的执行合约看起来会是这样的:

contract ImplementationV2 is ImplementationV1 {uint public totalPlayers;function addPlayer(address _player, uint _points)public onlyOwner{require (points[_player] == 0);points[_player] = _points;totalPlayers++;}}

而要升级这个新的执行合约,我们需要做的,就是在网络上部署这个合约,然后,嗯你猜对了,就是调用代理合约的upgradeTo(address)函数,并同时pass掉我们新执行合约的地址。现在,我们的合约已演变为能够保持跟踪 totalPlayers,同时仍然为用户提供相同的地址。

这种方法是强大的,但也存在着一些局限性。主要关注的一点是,代理合约拥有者(proxyOwner)有太多的权力。而且,这种方法对复杂的系统而言是不够的。对于构建具有可升级合约的 dApp而言,组合主从合约以及非结构化可升级存储代理合约,会是更为灵活的一种方法,这也是作者所在的GovBlocks所使用的方法。

结论

非结构化存储代理合约,是创建可升级智能合约最先进的技术之一,但它仍然是不完美的。毕竟,我们并不希望dApp所有者对dApp具有不当的控制权。如果开发者拥有了这种权力,那这个dapp还能称之为去中心化应用吗?在这里,我建议读者可以阅读下Nitika提出的反对使用onlyOwner的论点。你也可以在GitHub上窥探到我们的代理合约。

希望这篇文章可以帮助你创建可升级的智能合约。

同时向Zepplin在代理技术方面进行的工作蓑衣网小编2022致敬。

欢迎你给出自己的看法。[x]
怎么样实现可升级的智能合约 | 分享给朋友: