6

I want to write a contract that uses a library. This library should be exchangable.

Meaning I want the contract to have an updatable (only by owner) address variable always pointing to the current version of the library.

Thus I could write and deploy a new library and just tell the contract the address where the the new library is. No need to update and re-deply the whole contract.

I'm sure it's possible but I can't figure out the right syntax/architecture.

  • There's no built in way to do this, but you can do it manually using delegatecall. This question asks ubout upgradability in more general terms: ethereum.stackexchange.com/questions/2404/… – Tjaden Hess Dec 8 '16 at 18:43
  • Also, this blog post has some good tips: blog.colony.io/… – Tjaden Hess Dec 8 '16 at 18:46
  • Thank you! I had already read these. What I still don't get is how to actually update the library a contract uses. The Colony blog post describes the architecture really well but how would I than go on and change the library. – user5407 Dec 8 '16 at 19:49
5

Here's a sketch of a way to do it, but be warned this isn't for the faint of heart.

  1. Create a base contract with a global variable that will point to the current library.
  2. Create the contract (the host) inheriting from the base contract. Also create the first real library for it.
  3. Create a library (the proxy) inheriting from the base contract. (This will allow it to read the host contract's variable) This library should only have a fallback function, which immediately uses delegatecall to send the calldata to the current library.
  4. Deploy all the contracts, linking the proxy to the host contract. Solidity won't know that the proxy library doesn't actually implement the actual library's interface--all it sees is the address.
  5. When you want to upgrade the library, just change the global variable in the host contract.

As I warned, this is not for the faint of heart. You'll probably bash your head against all kinds of walls, desks, and miscellaneous hard surfaces getting this all to work. Then you might calculate the effort involved in this, as opposed to just redeploying the contracts in the first place. Unless you really, really don't want to redeploy the contracts, you'd probably be better off just doing that.

  • 1
    The name doesn't matter at all. All that matters is what position it is in. The best way to ensure the position is the same is to make the proxy inherit from the main contract – Tjaden Hess Dec 8 '16 at 23:53
  • Huh. All this time I thought it was sha3(variable_name). I'll update my answer. – Matthew Schmidt Dec 9 '16 at 2:05
  • You're likely thinking of dynamically sized variables, like arrays strings. Those start at sha3(position), but the name still doesn't matter. The only names that matter to the compiler are function names – Tjaden Hess Dec 9 '16 at 2:14
  • Thank you. I really didn't expected it to be so cumbersome.The reason I don't want to re-deploy the contract is that I want to preserve the storage of the contract. (And I wanted the contract to be found always under the same address. But the ENS will kind of solve this) – user5407 Dec 9 '16 at 2:49
1
/**
 * 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;
    }
}

Source : https://gist.github.com/Arachnid/4ca9da48d51e23e5cfe0f0e14dd6318f

  • Consider editing your answer to add some commentary. While direct code is helpful, it's good to have an explanation as well. – flygoing Jan 16 '18 at 14:48
  • 1
    Commentary is in comments? – neutral Jan 16 '18 at 18:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.