The More-Minimal Proxy

0
102

If you’ve ever looked into how to cheaply deploy contracts at scale, you’ve probably encountered EIP-1167. These minimal proxy contracts perform a DELEGATECALL to a fixed address, and they do it with only 45 bytes (or even less if you’re proxying to a compact address that starts with zero bytes), which makes it much more efficient to deploy, with the tradeoff of some increased overhead to use it thereafter.

The standard is proliferating, and with good reason — it’s a great idea for a set of contracts with duplicate logic to only have that logic to live in one place (as long as nobody takes that logic and accidentally kills it, that is). For example, it’s now part of the OpenZeppelin SDK (they just published a great primer on how EIP-1167 works). Hell, I myself wrote a library a while back for the Erasure protocol called Spawner that spits out EIP-1167 proxies. Point being, this EIP is now in single-digit company of ERCs that are actually being adopted in the wild.

And that’s why I want to address something that’s been bothering me about EIP-1167 before it reaches critical mass. Namely, this: it’s not as minimal as it could be.

Here’s the bytecode in question, spelled out in the parlance of the EIP itself:

0x363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3
pc    op / pushdata  opcode              stack (top on the left)
---- ------------- ------------------ -----------------------
0x00 36 calldatasize cds
0x01 3d returndatasize 0 cds
0x02 3d returndatasize 0 0 cds
0x03 37 calldatacopy
0x04 3d returndatasize 0
0x05 3d returndatasize 0 0
0x06 3d returndatasize 0 0 0
0x07 36 calldatasize cds 0 0 0
0x08 3d returndatasize 0 cds 0 0 0
0x09 73bebebebebe. push20 0xbebebebe 0xbebe 0 cds 0 0 0
0x1e 5a gas gas 0xbebe 0 cds 0 0 0
0x1f f4 delegatecall suc 0
0x20 3d returndatasize rds suc 0
0x21 82 dup3 0 rds suc 0
0x22 80 dup1 0 0 rds suc 0
0x23 3e returndatacopy suc 0
0x24 90 swap1 0 suc
0x25 3d returndatasize rds 0 suc
0x26 91 swap2 suc 0 rds
0x27 602b push1 0x2b 0x2b suc 0 rds
0x29 57 jumpi 0 rds
0x2a fd revert
0x2b 5b jumpdest 0 rds
0x2c f3 return

So what jumps out at you when you scan through this? I’ll tell you what jumps out at me — the swap opcodes. We can do better:

0x3d3d3d3d363d3d37363d73bebebebebebebebebebebebebebebebebebebebe5af43d3d93803e602a57fd5bf3
pc    op / pushdata  opcode              stack (top on the left)
---- ------------- ------------------ ------------------------
0x00 3d returndatasize 0
0x01 3d returndatasize 0 0
0x02 3d returndatasize 0 0 0
0x03 3d returndatasize 0 0 0 0
0x04 36 calldatasize cds 0 0 0 0
0x05 3d returndatasize 0 cds 0 0 0 0
0x06 3d returndatasize 0 0 cds 0 0 0 0
0x07 37 calldatacopy 0 0 0 0
0x08 36 calldatasize cds 0 0 0 0
0x09 3d returndatasize 0 cds 0 0 0 0
0x0a 73bebebebebe. push20 0xbebebebe 0xbebe 0 cds 0 0 0 0
0x1f 5a gas gas 0xbebe 0 cds 0 0 0 0
0x20 f4 delegatecall suc 0 0
0x21 3d returndatasize rds suc 0 0
0x22 3d returndatasize rds rds suc 0 0
0x23 93 swap4 0 rds suc 0 rds
0x24 80 dup1 0 0 rds suc 0 rds
0x25 3e returndatacopy suc 0 rds
0x26 602a push1 0x2a 0x2a suc 0 rds
0x28 57 jumpi 0 rds
0x29 fd revert
0x2a 5b jumpdest 0 rds
0x2b f3 return

See? One less swap, and a returndatasize instead of a dup to boot. This translates to 200 less gas to deploy (one fewer byte of runtime code) and 4 less gas to call (the swap we got rid of costs three gas, and returndatasize costs one less gas than dup).

It’s objective improvement, but is it worth the extra effort to update all the tooling, or maybe even modify the EIP or draft a new one? I have no idea. Let me know if you have strong opinions one way or the other.


The More-Minimal Proxy was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.