W3Schools



如果合同签发者希望有办法升级合同代码,以便账户数据和其他东西继续存在,那么以太坊可以提供吗?也可以在不更改合同地址的情况下完成此操作,还是总是需要发布新地址?

是否存在“附件”机制,在没有完全重写的情况下为合同添加一些新功能?

soliditycontract-developmentcontract-designupgradingdesign-patterns
8个回答
111

一旦合同在区块链中,它就是最终的,不能更改。当然,如果允许某些参数通过原始代码进行更改,则可以更改这些参数。

更新合同的一种方法是使用版本控制系统。例如,您可以拥有一个入口合同,该合同只是将所有调用转发到最新版本的合同,由可更新地址参数定义。您还可以使用名称注册表,并将其更新为指向最新的合同版本。

另一种方法是将您的逻辑代码放在库中,然后通过Solidity中的库使用CALLCODE功能来调用位于指定的可更新地址的代码。这样,用户数据在版本之间保持不变。这具有以下限制:逻辑契约的ABI必须随时间保持相同。

宅基地编辑:

从Homestead发布开始,现在有一个DELEGATECALL opcode. This allows you to essentially forward calls to a separate contract while maintaining msg.sender and all storage.

例如,您可以拥有一个维护相同地址和存储的合同,但将所有调用转发到存储在变量中的地址:

contract Relay {
    address public currentVersion;
    address public owner;

    function Relay(address initAddr){
        currentVersion = initAddr;
        owner = msg.sender;
    }

    function update(address newAddress){
        if(msg.sender != owner) throw;
        currentVersion = newAddress;
    }

    function(){
        if(!currentVersion.delegatecall(msg.data)) throw;
    }
}

这里的 an old gist I used to demonstrate data/code segregation a while ago.


25

如何使用IPFS获取文档的哈希值并将其存储在私有区块链中(使用go-ethereum)?将松露框架和可靠性视为契约导向语言。


28

一种方法是使用如下所述的合同系统:

  1. 合同“注册” - 将包含系统所有合同的“名称 - 地址”对;
  2. 合同Backend;
  3. 合同Frontend using Backend;
  4. 部署Register & get address of it;
  5. 部署Backend & register address of Backend into already deployed Register;
  6. 硬编码地址Register into source of Backend. Before any call Backend from Frontend you should call your Register and get the actual address of Backend.

然后你可以更新你的Backend contract any time - simply deploy the new one and re-register them in the Register.

致电外部合同:solidity.read the doc S.org...

另见论坛讨论:forum.ether EU M.org...


9

@Nick Johnson有一个可升级合同的基本合同。

他说, before using one should be "fully understanding the limitations and drawbacks."

/**
 * Base contract that all upgradeable contracts should use.
 * 
 * Contracts implementing this interface are all called using delegatecall from
 * a dispatcher. As a result, the _sizes and _dest variables are shared with the
 * dispatcher contract, which allows the called contract to update these at will.
 * 
 * _sizes is a map of function signatures to return value sizes. Due to EVM
 * limitations, these need to be populated by the target contract, so the
 * dispatcher knows how many bytes of data to return from called functions.
 * Unfortunately, this makes variable-length return values impossible.
 * 
 * _dest is the address of the contract currently implementing all the
 * functionality of the composite contract. Contracts should update this by
 * calling the internal function `replace`, which updates _dest and calls
 * `initialize()` on the new contract.
 * 
 * When upgrading a contract, restrictions on permissible changes to the set of
 * storage variables must be observed. New variables may be added, but existing
 * ones may not be deleted or replaced. Changing variable names is acceptable.
 * Structs in arrays may not be modified, but structs in maps can be, following
 * the same rules described above.
 */
contract Upgradeable {
    mapping(bytes4=>uint32) _sizes;
    address _dest;

    /**
     * This function is called using delegatecall from the dispatcher when the
     * target contract is first initialized. It should use this opportunity to
     * insert any return data sizes in _sizes, and perform any other upgrades
     * necessary to change over from the old contract implementation (if any).
     * 
     * Implementers of this function should either perform strictly harmless,
     * idempotent operations like setting return sizes, or use some form of
     * access control, to prevent outside callers.
     */
    function initialize();

    /**
     * Performs a handover to a new implementing contract.
     */
    function replace(address target) internal {
        _dest = target;
        target.delegatecall(bytes4(sha3("initialize()")));
    }
}

/**
 * The dispatcher is a minimal 'shim' that dispatches calls to a targeted
 * contract. Calls are made using 'delegatecall', meaning all storage and value
 * is kept on the dispatcher. As a result, when the target is updated, the new
 * contract inherits all the stored data and value from the old contract.
 */
contract Dispatcher is Upgradeable {
    function Dispatcher(address target) {
        replace(target);
    }

    function initialize() {
        // Should only be called by on target contracts, not on the dispatcher
        throw;
    }

    function() {
        bytes4 sig;
        assembly { sig := calldataload(0) }
        var len = _sizes[sig];
        var target = _dest;

        assembly {
            // return _dest.delegatecall(msg.data)
            calldatacopy(0x0, 0x0, calldatasize)
            delegatecall(sub(gas, 10000), target, 0x0, calldatasize, 0, len)
            return(0, len)
        }
    }
}

contract Example is Upgradeable {
    uint _value;

    function initialize() {
        _sizes[bytes4(sha3("getUint()"))] = 32;
    }

    function getUint() returns (uint) {
        return _value;
    }

    function setUint(uint value) {
        _value = value;
    }
}

2

允许您签订具有稳定地址的合同,但具有完全可控和可升级的行为。

HTTPS://GitHub.com/U2/ether-router

HTTPS://GitHub.com/con森sys/smart-contract-best-practices#upgrading-broken-contracts


4

我们(我和我的团队)最近在提到colony.io的帖子后,已经开始研究可升级合同问题了可升级合同. So, we came with a solution in which we have different layers of contract rather than having one single Contract.

如果我简要地描述它,那么需要使存储部分非常通用,这样一旦你创建它,你可以将每种类型的数据存储到它(借助于setter方法)并访问它(借助getter方法) 。这使您的数据存储永恒,您将来不需要更改。

查看此数据存储区合同以更好地了解它 - HTTPS://个噢噢.公里/AL MV J5

第二层应该是与您的功能的主要合同,可以在以后升级,并且为了使用旧数据存储,您应该签订合同,以便您可以将新部署的合同指向现有(旧)数据存储然后您可以在新合同正确地与旧数据存储区对话后终止旧合同。

查看我们的代码库,了解我们如何实施可升级合同 - HTTPS://个噢噢.公里/怕5ZG EV

注意:在上面的GitHub repo中,由于我们的用例,我们使用了三层契约。但是,可以仅使用两层进行合同升级。

希望这可以帮助。


5

在部署之后,无法修改以太坊的基本原则之一即智能合约。

, you can still have Upgradable Smart Contracts if you put the following into consideration

这必须从一开始就计划好。 The key point is number 4. But all the others are essential to have a real and smooth Smart Contract Upgrade.

因此,您需要考虑以下5点来设计您的智能合约:

  1. 保持智能合约模块化和相当独立的数据结构规则和逻辑。 So if you will need to change something, you will change just the related contract and you will not need to change many or all contracts.
  2. 你应该做好准备紧急停止或断路器 to be able to stop all operations during any migration. Because you do not want to be in a situation where people can still update/insert data to the old version of the smart contract while you are migrating and thereafter.
  3. 你应该之前提供了从智能合约中读取所有数据的功能. Of course you can do a permissioned read by restricting reading all data to the owner or any other trusted user or even another smart contract. You will need to read from the old version of your smart contract and insert in the new version.
  4. 您将使用以下策略与智能合约进行通信。我复制了它们智能联系最佳实践:

升级破损的合同

如果发现错误或需要,则需要更改代码   需要改进。发现一个bug是不好的,但是   无法处理它

...

但是,有两种最常用的基本方法。   两者中较简单的是拥有一个持有的注册管理机构合同   合同的最新版本的地址。更无缝   合同用户的方法是签订转发呼叫的合同   和数据到最新版本的合同。

示例1:使用注册表合同来存储最新版本的合同

在此示例中,不转发呼叫,因此用户应该获取   每次与当前地址交互之前的当前地址。

contract SomeRegister {
    address backendContract;
    address[] previousBackends;
    address owner;

    function SomeRegister() {
        owner = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == owner)
        _;
    }

    function changeBackend(address newBackend) public
    onlyOwner()
    returns (bool)
    {
        if(newBackend != backendContract) {
            previousBackends.push(backendContract);
            backendContract = newBackend;
            return true;
        }

        return false;
    }
}

这种方法有两个主要缺点:

  1. 用户必须始终查找当前地址以及任何失败的人   这样做有使用旧版本合同的风险

  2. 您需要仔细考虑如何处理合同   替换合同时的数据

另一种方法是将合同转发呼叫和数据发送给   合同的最新版本:

示例2:使用DELEGATECALL转发数据和调用

contract Relay {
    address public currentVersion;
    address public owner;

    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }

    function Relay(address initAddr) {
        currentVersion = initAddr;
        owner = msg.sender; // this owner may be another contract with multisig, not a single contract owner
    }

    function changeContract(address newVersion) public
    onlyOwner()
    {
        currentVersion = newVersion;
    }

    function() {
        require(currentVersion.delegatecall(msg.data));
    }
}

这种方法避免了以前的问题,但存在问题   拥有。您必须非常小心地将数据存储在此中   合同。如果您的新合同的存储布局不同于   首先,您的数据可能最终被破坏。另外,这很简单   模式的版本不能仅从函数返回值   转发它们,这限制了它的适用性。 (更复杂   实现 attempt to solve this with in-line assembly code and a registry of return sizes.)

无论您的方法如何,都有一些方法是很重要的   升级你的合同,否则他们将无法使用   在他们身上发现了不可避免的错误。

但是,我也建议检查Solidity中的代理库 that is posted by Zeppelin Solutions and Aragon. There is a planning to make an industry standard for this matter.

  1. 你必须有一个良好的测试策略和策略. Because the cost of updating the your smart contract can really ruin your life.

我为此创建了一个关于标题的故事:以太坊dApps的基本设计考虑因素(1):可升级的智能合约 and I provided a sample for every point of the above 5.