如果你很熟悉以太坊(或者区块链),也许你听过这个词“不可篡改”。当我们想到区块链的时候,我们大多会将区块链和账本和系统的功能状态不能改变或删除相联系。特别是想到智能合约的时候,我会把这个定义和“不可改变对象”相比较。
在面向对象的功能性程序中,不可变对象指的是在生成后状态不可以改变的对象。
这意味着一旦我们的合约部署在区块链上,就被限定死了。我们不能在预先设定好的内容之外对状态进行改变,但是最重要的是,我们也不能修改代码。
为了解决这个问题,我们会讨论其中一个基于分散合约存储和逻辑到不同合约的解决方案,同时通过注册表指导命令。最后我们会讨论好处和坏处分别是什么。
用合约管理合约(CMC)
这个模型会基于Monax的五步模型,为最新的Solidity语言功能更新,使之更容易理解和阅读。我们会实施以下合约:
1 CMC- 追踪系统里合约的注册表
2 控制器 – 合约运行在我们存储的合约之下
3 存储 – 数据存储合约并且带有必要的getters和setters
4 ALC – 带有应用逻辑的智能合约,用户切入点
如果您有Web应用程序的经验,那么动作流看起来就像命名的那样。
用户→ALC→控制器→存储
ALC的基本级别,控制器和存储器
我们加入在注册表上的每个合约都会从基本级别开始,在此级别上为我们的注册表设定和限制以太坊地址,在修改器上设定合约准入控制,同时在注册表上添加接口以便从合约上获得地址,还有添加删除合约的选项。
pragma solidity ^0.4.19; interface ContractProvider { function contracts(bytes32 _name) external returns (address); }
pragma solidity ^0.4.19; import './ContractProvider.sol'; /** * Base class for every contract (DB, Controller, ALC,) * Once the CMC address being used by our newly added contract is set it can not be set again except by our CMC contract **/ contract CMCEnabled { address public CMC; modifier isCMCEnabled(bytes32 _name) { require(msg.sender == ContractProvider(CMC).contracts(_name)); _; } function() external { revert(); } function setCMCAddress(address _CMC) external { if (CMC != 0x0 && msg.sender != CMC) { revert(); } else { CMC = _CMC; } } function changeCMCAddress(address _newCMC) external { require(CMC == msg.sender); CMC = _newCMC; } function kill() external { assert(msg.sender == CMC); selfdestruct(CMC); } }
合约注册表
我们的合约注册表会在映射中跟踪系统现在使用的合约地址。我们会使用32字节的变量因为不能将动态类型(字符串)作为密钥。然后我们可以添加需要添加的功能,从现有的注册表移除一些功能,并且更新为我们所要部署的合约中的注册表。
pragma solidity ^0.4.19; import './CMCEnabled.sol'; import './Ownable.sol'; contract CMC is Ownable { mapping (bytes32 => address) public contracts; function addContract(bytes32 _name, address _address) external onlyOwner { CMCEnabled _CMCEnabled = CMCEnabled(_address); _CMCEnabled.setCMCAddress(address(this)); contracts[_name] = _address; }
function getContract(bytes32 _name) external view returns (address) { return contracts[_name]; }
function removeContract(bytes32 _name) external onlyOwner returns (bool) { require(contracts[_name] != 0x0); CMCEnabled _CMCEnabled = CMCEnabled(contracts[_name]); _CMCEnabled.kill(); contracts[_name] = 0x0; }
function changeContractCMC(bytes32 _name, address _newCMC) external onlyOwner { CMCEnabled _CMCEnabled = CMCEnabled(contracts[_name]); _CMCEnabled.changeCMCAddress(_newCMC); } }
很好!现在就是演示它如何工作的时刻了,为了完成我们需要创造一个简单的工作流程,其中在我们的存储合约中通过一个ALC和控制器存储着变量x。每个合约都会被分开部署,并且添加到新建的注册表。惯例也许是给予你的合约和之前定义时同样的名字。
pragma solidity ^0.4.19; import './CMCEnabled.sol'; contract Storage is CMCEnabled { uint public x; function setX(uint _x) external isCMCEnabled("Storage") { x = _x; } } contract Controller is CMCEnabled { function setX(uint _x) external isCMCEnabled("UserEntry") { Storage(ContractProvider(CMC).contracts("Storage")).setX(_x); } } contract UserEntry is CMCEnabled { function setX(uint _x) external { Controller(ContractProvider(CMC).contracts("Controller")).setX(_x); } }
进行测试
在github上的报告中,你可以找到包含一些基本单元测试的测试文件。如果想要运行,需要确保安装了truffle和Ganache运行。启动测试命令从而运行测试单元:
优势和劣势
这种设定的两种好处当然是代码的维护性和可更新性。我们现在可以在现有的存储器上搭建新的控制器或者ALC,从而用户的数据可以完好无损。
最大的缺点就是燃料费用的消耗。部署这样的系统会比单纯部署一个合约耗费更多,特别是这里的工作流程还这样简单。不仅如此,在对状态进行更改时,用户需要花费10%更多的燃料费用。
我明白对于用户来说,可能会有一些信任问题,我不想看到这样的争议,因为其实他们可以很容易地去检查注册表找到正在运行的合约。