2key Network: Immutability vs Upgradability

One of the major benefits of the blockchain is its immutability. Providing an immutable, timestamped, and unforgeable record of data. In the Ethereum blockchain, smart contracts, most often programmed in Solidity, are themselves stored on the blockchain, and hence are immutable. A smart contract can modify its storage as part of a transaction, with the new updated contract state stored in the blockchain. However, the contract itself cannot be modified.

This immutability of smart contracts means that in contrast to other code, contracts cannot be updated and upgraded. So the common practice of software update and upgrade cannot be applied to a smart contract per se.

Naturally, bugs, improvements, and changing requirements would need some way to modify the collection of contracts comprising a blockchain application. Such a blockchain application can be either a pure blockchain application or more combined web and blockchain application commonly called a Dapp (decentralized app).

Forever Immutable Logic ?

Approaches for Contract Upgrade

Three approaches for contract upgrade have been proposed in general. The article Essential Design Considerations for Ethereum ÐApps (1): Upgradeable Smart Contracts summarizes these approaches well. In practice, all these approaches are to be taken together, either with manual coding or in constructing a smart contract upgrade framework.

Modularization of Contracts

Both for upgradability and for following good software engineering practices, separate the data and the logic of a contract.

For each significant contract, split the contract logic, both the business logic and the authorization (in the form of access modifiers) into separate contracts.

Such an approach would enable a separate upgrade of the data and the logic.

Migration of Data

Pull the data from the old contract, write it into the new contract, and just forget the old contract.

Such an approach requires stopping the old contract with a circuit breaker function. However, such an approach is clearly problematic for a Dapp, like shutting an ordinary web application server, it leaves users hanging loose without the functionality.

Wrap the Contract with a Proxy

Each smart contract is wrapped by a proxy for upgrades. Thus, we always deploy two contracts. The first contract is a simple wrapper or “proxy” which users interact with directly and is in charge of forwarding transactions to and from the second contract, which contains the logic. The key concept to understand is that the logic contract can be replaced while the proxy or the access point is never changed. Both contracts are still immutable in the sense that their code cannot be changed, but the logic contract can simply be swapped by another contract. The wrapper can thus point to a different logic implementation and in doing so, the software is “upgraded”.

The basic pattern take from Essential Design Considerations for Ethereum ÐApps (1): Upgradeable Smart Contracts is:

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));
}
}

The proxy pattern has been generally accepted by the community as a generally applicable solution.

The ZeppelinOS Upgrade Pattern

ZeppelinOS is a platform to develop, deploy and operate smart contract projects on Ethereum and every other EVM and eWASM-powered blockchain.

ZeppelinOS Upgrades Pattern generalizes the proxy wrapper idea to be an integral part of a whole development framework, that includes a variation of the well-known Truffle Framework rewritten to support upgradability.

The basic idea is:

User ---- tx ---> Proxy ----------> Implementation_v0
|
------------> Implementation_v1
|
------------> Implementation_v2

Proxy Forwarding

Forwarding the calls from the proxy to the contract is done as:

assembly {
let ptr := mload(0x40)
  // (1) copy incoming call data
calldatacopy(ptr, 0, calldatasize)
  // (2) forward call to logic contract
let result := delegatecall(gas, _impl, ptr, calldatasize, 0, 0)
let size := returndatasize
  // (3) retrieve return data
returndatacopy(ptr, 0, size)
  // (4) forward return data back to caller
switch result
case 0 { revert(ptr, size) }
default { return(ptr, size) }
}

This code can be put in the fallback function of a proxy and will forward any call to any function with any set of parameters to the logic contract without it needing to know anything in particular of the logic contract’s interface.

Structuring Storage

Using a delegatecall means the call is executed in the context of the proxy contract. Hence, ZeppelinOS uses a novel unstructured storage approach to make sure the implementation contract storage does not override the proxy contract storage.

Special precautions in terms of storage layout are suggested to make sure an upgrade is possible.

Upwards & Onwards

The 2key Network Approach to Upgrade

2key Network is a blockchain-based referral network intended to reward referrers through smart contracts. Referrals progress through the network thanks to people sharing them through their regular browsers. Therefore, 2key generates off-chain cryptographically signed links, which propagate among users without reaching the blockchain. Upon conversion, a user submits the signed link to the smart contract. The smart contract rewards the chain of referrers as represented in the signed link. The smart contract is deployed per referral campaign by the campaign initiator, depositing a referral reward, as an amount of cryptocurrency, from which the smart contract will later pay the referrers.

Persistent vs Ephemeral Contracts

2key Network collection of smart contracts is broadly divided into two parts:

  • Persistent Contracts: These contracts manage the whole ecosystem, and serve as singletons — each one has only one active instance deployed and serving the entire network at any given time. This includes the 2KEY Token Economy Contract which is an ERC20 based contract, as specified in the ERC20 Token Standard, the 2key Governance Contract, modelled after the well-known How to build a DEMOCRACY on the blockchain guide of Ethereum. Other singleton contracts on the network include the liquidity pool exchange contracts, the congress contracts, the user and reputation registry contract and others. All these, except the economy contract itself, require upgradability in order to support a viable software lifecycle, as the functionalities and optimisations of the network evolve.
  • Ephemeral Contracts: The contract of a campaign is intended for the duration of the campaign and that’s it. Business-wise a campaign implements a pre-agreed logic that is not going to change. However, such a contract will include references and hence calls to the permanent contracts.

Upgrade the Persistent Contracts

It may be projected how ERC20 types of contracts and governance contracts may evolve, and build special upgrade mechanisms into these. But, as 2key Network develops novel economic incentive mechanisms to be implemented in its reputation model and incentive model, it is hard to foresee the corresponding contracts are going to evolve. Also, as the network matures, its governance mechanisms should become more distributed. In general, to support a viable evolution for the network, singletons must support upgradability.

Consequently, we’re implementing a novel modification over the proxy pattern as our mechanism for the upgrade of the persistent contracts. In the next posts we’ll share more in depth how the 2key upgradability schema works, and how it combines with a novel CICD pipeline for smart contracts to upgrade the 2key-protocol npm package used by the 2key browser nodes.

Throw the Ephemeral After Use

The campaign contracts are created anew per campaign, and as part of the conversion process, creates escrow contracts to temporarily hold the tokenized assets purchased by the converters.

Examining the whole collection of contracts around a campaign, these are all deployed from our Dapp for a campaign. So since business-wise this collection of contract deployed per campaign cannot change, we do not have a need to upgrade this collection.

These ephemeral contracts remain viable while active, then archived. New contract versions for contracts can serve future campaigns, but as a campaign life cycle is limited, it does not need upgradability by itself. The campaign contracts do hold references and interact with the network’s persistent contracts, but this will be a reference to the corresponding proxies, which enable them to always contact the updated persistent network logic.

Conclusion

We believe the 2key Network split into persistent and ephemeral contracts is a good design approach for Dapps in general and especially for mature scalable Dapps. This split between a persistent network core and transient ephemeral contracts is a novel practice that is not encountered even in ordinary software. It is clearly distinct from the well-known core vs plugins approach.