Skip to content

The Bank Proxy API Model

zikes edited this page Sep 19, 2022 · 2 revisions

The "proxy" contract model uses two contracts: a dedicated Proxy and an external API. The Proxy calls out to the API for business logic, creating a system that can be upgraded without losing the Proxy contract's stored data by updating the address of the external API within the Proxy.

The Proxy contract will be both the access point and the data store for the overall "Bank" system.

The API contracts are where any complex data operations should take place. They can be replaced at minimal cost, and without needing to re-deploy the original Proxy contract, therefore losing access to the data it stores or performing a costly migration.

Because the contracts are fully separated, rollback operations are also supported. If API v3 introduces a new bug, the Proxy contract can "upgrade" back to API v2.

                                                       ┌────────────┐
                                                       │            │
                                                 ┌────►│   API_v1   │
                                                 │     │            │
                                                 │     └────────────┘
                                                 │
 ┌────────────┐       ┌───────────┐              │     ┌────────────┐
 │            │       │           │              │     │            │
 │  Customer  ├──TX──►│   Proxy   ├─delegatecall─┼────►│   API_v2   │
 │            │       │           │              │     │            │
 └────────────┘       └───────────┘              │     └────────────┘
                                                 │
                                                 │     ┌────────────┐
                                                 │     │            │
                                                 └────►│   API_v3   │
                                                       │            │
                                                       └────────────┘

A caveat is that the storage models for both the Proxy and the API must match almost exactly. This is because the methods in the API expect the variables it interacts with to be at certain points in the contract's storage.

Smart contract storage is a largely "slots" based system. Each slot can potentially contain multiple variables, if they are small enough to fit, but they will always be allocated according to the order in which they are declared and not via any automated optimization strategy. This makes the allocations predictable.

Because they are predictable, as long as the same variables are declared in the same order for both the Proxy and the API, they will match up for the purposes of the delegatecall.

+--------------+------------+------------+
| Storage Slot | Proxy      | API        |
+--------------+------------+------------+
| Slot 1       | Variable 1 | Variable 1 |
+--------------+------------+------------+
| Slot 2       | Variable 2 | Variable 2 |
+--------------+------------+------------+
| Slot 3       | Variable 3 | Variable 3 |
+--------------+------------+------------+

A mismatch will cause the delegatecall to reference the wrong location in storage, possibly corrupting the data.

+--------------+------------+------------+
| Storage Slot | Proxy      | API        |
+--------------+------------+------------+
| Slot 1       | Variable 1 | Variable 1 |
+--------------+------------+------------+
| Slot 2       | Variable 2 | Variable 3 | <- Mismatch, delegatecall trying to reach
+--------------+------------+------------+    Variable 3 will instead be working with
| Slot 3       | Variable 3 |            |    Variable 2.
+--------------+------------+------------+

Initial Setup

The make commands below are to be run from the repository root. They will assume you have already followed the instructions at Getting Started to install the appropriate tooling and run a local geth instance. This also assumes you have run make geth-deposit to load the geth accounts with Eth.

Building the Bank Proxy and APIs

The Proxy smart contract can be found in app/bank/proxy/contract/src/bank.sol.

The API smart contracts can be found in app/bank/proxy/contract/src/api/{v1,v2,v3}/api.sol.

To build all of the contracts, run the following make command.

$ make bank-proxy-build

Deploying the Bank Proxy and APIs

The initial deployment will include the v1 API and the Bank Proxy.

$ make bank-proxy-deploy

Upgrading the Proxy's API

The Proxy stores the address of the API it uses to perform delegatecalls. An API update simply exchanges the old address for a new one.

To upgrade the Proxy to API v2, run the following command.

$ make bank-proxy-api-v2-deploy
...
Logs
----------------------------------------------------
upgrade[eb380d740ec33adf803abe0d6b14ee29ae6194a9] success[true] version[ 0.2.0]
...

A successful upgrade will log the address and version of the new API contract, as shown above.

Loading the Bank's Account Balances

The makefile includes a target to load the bank's account with some various balances. These balances are mostly arbitrary, but will allow for the demonstration of the reconcile and account balance checks later.

$ make bank-proxy-load

Checking the Bank's Account Balances

The makefile includes a target to check the balances of all of the accounts.

$ make bank-proxy-balances

Reconciling Winner/Losers

The makefile includes a target to perform the Reconcile. The Reconcile specifies a winner, an array of losers, and the logic and details for how to distribute winnings. Although this calls the Reconcile of the Proxy contract, the logic is stored within the API contract, meaning it can be upgraded to accommodate bug fixes and rules changes.

$ make bank-proxy-reconcile