Skip to content
This repository has been archived by the owner on Mar 18, 2019. It is now read-only.

Upgradeability with Proxies

Brecht Devos edited this page Dec 14, 2018 · 1 revision

We can use the Proxy pattern (unstructured storage seems to be the cleanest pattern) to make it easier to upgrade parts of the protocol. The proxy pattern helps in two ways:

  • The data stored in the contract can be used by the new contract
  • The address of the contract remains the same after updating

It's easy to use the pattern (almost no changes needed to the current code), gas consumption increases by about ~1400 gas for every call to the contract through the proxy (I tested this by adding a Proxy for TradeDelegate).

TradeDelegate

TradeDelegate doesn't use any storage, but it is used by users for ERC20 allowances (and in the future as an operator that needs to be authorized). So keeping the same contract address when updating the contract is very useful.

TradeDelegate is called twice in submitRings (once for doing the token transfers, once in FeeHolder to check the authorized addresses. FeeHolder could store its own list of authorized addresses to reduce this to once).

Two possible ways we can upgrade the contract:

Single method

We can update the batchTransfer itself. batchTransfer could work differently between protocol versions by checking msg.sender:

if (msg.sender == address(protocolV20) {
  // protocolV20 behaviour
} else if (msg.sender == address(protocolV21) {
  // protocolV21 behaviour
}

batchTransfer should take a single bytes input parameter to make this as easy as possible (which is almost what is done now, except a bytes32[]).

New methods

Instead of updating batchTransfer we could add new functions that are only used in newer versions of the protocol.

function batchTransferV20(bytes batch) {
    // Used in protocolV20
}
function batchTransferV21(bytes batch) {
    // Used in protocolV21
}

This is probably the cleanest way because we can simply inherit from the old contract when creating a new version while retaining all functionality in the old contract.

TradeHistory

We could not only update the logic, we can also store additional data without throwing away all data that was already stored in the contract. I think this could be very helpful.

TradeHistory is called twice in submitRings (once for getting the trade history, once for updating the trade history).

BurnRateTable

Stores some data, but currently not a lot so not that difficult if we would have to restore it some way in a new contract. May need updates in the future (like adding user locking of LRC, calling burn on the LRC contract instead of transferring to 0x0, burn rate percentages may need to be updated, ...) so it could be useful to use the Proxy pattern here as well, though not really needed.

BurnRateTable is called for every unique feeToken once in submitRings.

FeeHolder

This contract stores the fee balances and owns the fee funds. If we would upgrade the contract funds of the previous version would only be available in the old contract, which is only a minor inconvenience. I would say using a Proxy here is not needed.

FeeHolder is called once in submitRings.

OrderRegistry

Stores all registered order hashes. So the contract could store a lot of data, but there's only a small chance the logic and data in the contract would need updating, so using a Proxy shouldn't be needed.

OrderRegistry is called once for every order that does not provide a signature in submitRings.