• Stars
    star
    102
  • Rank 335,584 (Top 7 %)
  • Language
    Solidity
  • Created almost 3 years ago
  • Updated about 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Upgradeable proxy with the least possible gas overhead.

⚠️ This pattern may become unusable soon due to EIP-4758. See Caveat Emptor.

Cacheable Beacon Proxy

An upgradeable proxy with minimal overhead.

CacheableBeaconProxy.sol

Cacheable Beacon Proxy is a design for upgradeable proxies that extends the beacon proxy pattern with an efficient cache. While upgradeable proxies generally require at least 2100 gas for a cold storage access to load the implementation address, the cache used by this pattern has a much lower access cost of only 100 gas.

The cache is efficient because the cached value in fact never changes, although it can be temporarily invalidated.

On a cache miss, the proxy behaves like a regular beacon proxy: the associated beacon contract is consulted for the current implementation address and the function call is delegated to that address. This code path has the usual overhead of cold storage access (2100 gas), plus the extra beacon overhead of a call to a cold address (2600 gas).

This is not a regular beacon though, as it has the ability to deploy a clone of the current implementation at a fixed address. This is enabled by CREATE2 redeployments, a combination of SELFDESTRUCT and CREATE2 sometimes referred to as "metamorphic contracts".

The clone address is the value that can be immutably cached by the proxy. When the beacon is upgraded, the clone is selfdestructed in order to invalidate the cache. The beacon proxy considers the cache invalid if EXTCODESIZE returns 0 for the clone.

After an upgrade, the clone can be recreated by invoking the deployCache function on the beacon, allowing the proxy to use the efficient code path again.

Comparison

The table below shows the various patterns in use (and metamorphic contracts for comparison, though I don't think they have any real use) and how they fare in the key metrics that cause overhead over a non-proxied contract call.

These key metrics are the number of storage slots touched and the number of addresses touched, and they're important because each time one of these is touched for the first time in a transaction (i.e. a cold access) they incur significant additional cost: 2000 gas for storage and 2500 gas for addresses.

The Accesses column groups storage reads (SLOAD), "extcode" reads (EXTCODECOPY, EXTCODESIZE), and contract calls (STATICCALL, DELEGATECALL), all of which have a baseline cost of 100 gas.

Note: There are additional costs such as stack manipulation and memory use, but in theory these are relatively negligible.

Proxy Storage Touched Addresses Touched Accesses Gas Overhead
Transparent 2 (admin, impl) 1 (impl) 3 (sload, sload, delegatecall) 6800
UUPS 1 (impl) 1 (impl) 2 (sload, delegatecall) 4700
Beacon 1 (impl) 2 (beacon, impl) 3 (staticcall, sload, delegatecall) 7300
Blue-Green 0 2 (beacon, impl) 2 (extcodecopy, delegatecall) 5200
Cacheable Beacon 0 1 (cache) 2 (extcodesize, delegatecall) 2700
Metamorphic 0 0 0 0

The patterns that rely on metamorphic contracts as a building block, in a few exceptional situations degrade to the following worst-case characteristics.

Proxy Storage Touched Addresses Touched Accesses Gas Overhead
Blue-Green 0 3 (beacon 1, beacon 2, impl) 3 (extcodecopy, extcodecopy, delegatecalll) 7800
Cacheable Beacon 1 (impl) 3 (cache, beacon, impl) 4 (extcodesize, staticcall, sload, delegatecall) 9900

Prior Art

The use of metamorphic contracts for upgradeability has long been known, but they're arguably not enough on their own since the proxy is left in a broken state in the window between SELFDESTRUCT and redeployment.

Santiago Palladino proposed to fix this in a way akin to blue-green deployments, by keeping two beacons which can be selfdestructed in turns so that the proxy is never broken. This design works, but although it might sound like it should have similar best-case performance than the pattern described here, in fact it performs an additional cold address access that results in greater cost. Worst-case performance is better, due to not using storage even in this situation, and upgrading the proxy has significantly lower cost. However, there is no mechanism to ensure the two beacons are in sync, or even that they are not selfdestructed at the same time.

The design described here adds such a mechanism, offering a clean interface to execute upgrades that ensures the system is always kept in a consistent state. The main downside is that the cost of an upgrade will more or less double, given that the new implementation needs to be deployed and then cloned in order to reenable the cache.

Caveat Emptor

Is this a good idea? This pattern and metamorphic contracts in general rely on SELFDESTRUCT, a quirky EVM opcode that will likely be revised in the future, and possibly even removed. A post on this issue by Vitalik mentions upgradeability as one of the use cases, and one of the two proposals continues to allow it, while the other doesn't. Ultimately, it is unclear whether this pattern will work in the future, but in case it breaks, it may be advisable to include a way to permanently disable the cache and downgrade to a classic beacon proxy.

Update: As of October 2022, EIP-4758 Deactivate SELFDESTRUCT is being discussed for the upcoming Shanghai hard fork.

Additionally, because this pattern relies on cloning bytecode from one contract to another location, it will break some Solidity patterns that rely on the value of address(this) stored in an immutable variable.

More Repositories

1

hardhat-exposed

Automatically expose internal Solidity functions for smart contract testing.
TypeScript
82
star
2

erc721-list

JavaScript
28
star
3

netlify-latex

πŸ—žοΈ A Netlify template for LaTeX projects.
TeX
20
star
4

hardhat-linearization

Hardhat plugin to inspect linearization of Solidity contract.
TypeScript
12
star
5

hardhat-ignore-warnings

Hardhat plugin that adds ways to ignore Solidity warnings
TypeScript
12
star
6

node-gyp-cache

🌾 Improve your workflow by caching native dependencies.
TypeScript
9
star
7

solidity-fingerprint

Generate a fingerprint of a Solidity file that ignores comments and whitespace
Rust
9
star
8

parse-typed-args

🧒 CLI argument parser with accurate types.
TypeScript
8
star
9

eip712domains

A helper library and website for retrieval of the EIP-712 domain of a contract.
TypeScript
6
star
10

ripgrep.nvim

πŸ” A Neovim integration of the ripgrep search tool.
Lua
4
star
11

gas-bench

Solidity
4
star
12

extract-changelog

GitHub Action to extract the changelog section for the current version.
TypeScript
4
star
13

solidity-lexer

WIP Solidity lexer in JavaScript
TypeScript
3
star
14

template-typescript-ava

Project template using TypeScript and AVA.
TypeScript
2
star
15

disable-github-pjax

JavaScript
2
star
16

squashfs-smart-contract-sanctuary

Shell
1
star
17

markup-split

βœ‚οΈ Split a file into many parts.
TypeScript
1
star
18

test-typechain-upgrades

TypeScript
1
star
19

erc6909-extensions

ERC-6909 Usability & Security Extensions
Solidity
1
star
20

promisified

JavaScript
1
star
21

git-ignore

JavaScript
1
star
22

eslint-plugin-avoid-then

ESLint plugin to prefer await instead of then outside one-liners.
JavaScript
1
star
23

17h-53m-9OU

1
star