-
Notifications
You must be signed in to change notification settings - Fork 7
Upgradeability with Proxies
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 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:
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[]).
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.
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).
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
.
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
.
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
.