Removing trust from upgradeability proxy is necessary for anonymous developers. In order to accomplish this, instant and potentially malicious upgrades must be prevented. This EIP introduces additional storage slots for upgradeability proxy which are assumed to decrease trust in interaction with upgradeable smart contracts. Defined by the admin implementation logic can be made an active implementation logic only after Zero Trust Period allows.
Motivation
Anonymous developers who utilize upgradeability proxies typically struggle to earn the trust of the community.
Fairer, better future for humanity absolutely requires some developers to stay anonymous while still attract vital attention to solutions they propose and at the same time leverage the benefits of possible upgradeability.
Specification
The specification is an addition to the standard EIP-1967 transparent proxy design.
The specification focuses on the slots it adds. All admin interactions with trust minimized proxy must emit an event to make admin actions trackable, and all admin actions must be guarded with onlyAdmin() modifier.
Next Logic Contract Address
Storage slot 0x19e3fabe07b65998b604369d85524946766191ac9434b39e27c424c976493685 (obtained as bytes32(uint256(keccak256('eip3561.proxy.next.logic')) - 1)).
Desirable implementation logic address must be first defined as next logic, before it can function as actual logic implementation stored in EIP-1967 IMPLEMENTATION_SLOT.
Admin interactions with next logic contract address correspond with these methods and events:
// Sets next logic contract address. Emits NextLogicDefined
// If current implementation is address(0), then upgrades to IMPLEMENTATION_SLOT
// immedeatelly, therefore takes data as an argument
functionproposeTo(addressimplementation,bytescalldatadata)externalIfAdmin// As soon UPGRADE_BLOCK_SLOT allows, sets the address stored as next implementation
// as current IMPLEMENTATION_SLOT and initializes it.
functionupgrade(bytescalldatadata)externalIfAdmin// cancelling is possible for as long as upgrade() for given next logic was not called
// emits NextLogicCanceled
functioncancelUpgrade()externalonlyAdmin;eventNextLogicDefined(addressindexednextLogic,uintearliestArrivalBlock);// important to have
eventNextLogicCanceled(addressindexedoldLogic);
Upgrade Block
Storage slot 0xe3228ec3416340815a9ca41bfee1103c47feb764b4f0f4412f5d92df539fe0ee (obtained as bytes32(uint256(keccak256('eip3561.proxy.next.logic.block')) - 1)).
On/after this block next logic contract address can be set to EIP-1967 IMPLEMENTATION_SLOT or, in other words, upgrade() can be called. Updated automatically according to Zero Trust Period, shown as earliestArrivalBlock in the event NextLogicDefined.
Propose Block
Storage slot 0x4b50776e56454fad8a52805daac1d9fd77ef59e4f1a053c342aaae5568af1388 (obtained as bytes32(uint256(keccak256('eip3561.proxy.propose.block')) - 1)).
Defines after/on which block proposing next logic is possible. Required for convenience, for example can be manually set to a year from given time. Can be set to maximum number to completely seal the code.
Admin interactions with this slot correspond with this method and event:
Storage slot 0x7913203adedf5aca5386654362047f05edbd30729ae4b0351441c46289146720 (obtained as bytes32(uint256(keccak256('eip3561.proxy.zero.trust.period')) - 1)).
Zero Trust Period in amount of blocks, can only be set higher than previous value. While it is at default value(0), the proxy operates exactly as standard EIP-1967 transparent proxy. After zero trust period is set, all above specification is enforced.
Admin interactions with this slot should correspond with this method and event:
pragmasolidity>=0.8.0;//important
// EIP-3561 trust minimized proxy implementation https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3561.md
// Based on EIP-1967 upgradeability proxy: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1967.md
contractTrustMinimizedProxy{eventUpgraded(addressindexedtoLogic);eventAdminChanged(addressindexedpreviousAdmin,addressindexednewAdmin);eventNextLogicDefined(addressindexednextLogic,uintearliestArrivalBlock);eventProposingUpgradesRestrictedUntil(uintblock,uintnextProposedLogicEarliestArrival);eventNextLogicCanceled();eventZeroTrustPeriodSet(uintblocks);bytes32internalconstantADMIN_SLOT=0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;bytes32internalconstantLOGIC_SLOT=0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;bytes32internalconstantNEXT_LOGIC_SLOT=0x19e3fabe07b65998b604369d85524946766191ac9434b39e27c424c976493685;bytes32internalconstantNEXT_LOGIC_BLOCK_SLOT=0xe3228ec3416340815a9ca41bfee1103c47feb764b4f0f4412f5d92df539fe0ee;bytes32internalconstantPROPOSE_BLOCK_SLOT=0x4b50776e56454fad8a52805daac1d9fd77ef59e4f1a053c342aaae5568af1388;bytes32internalconstantZERO_TRUST_PERIOD_SLOT=0x7913203adedf5aca5386654362047f05edbd30729ae4b0351441c46289146720;constructor()payable{require(ADMIN_SLOT==bytes32(uint256(keccak256('eip1967.proxy.admin'))-1)&&LOGIC_SLOT==bytes32(uint256(keccak256('eip1967.proxy.implementation'))-1)&&NEXT_LOGIC_SLOT==bytes32(uint256(keccak256('eip3561.proxy.next.logic'))-1)&&NEXT_LOGIC_BLOCK_SLOT==bytes32(uint256(keccak256('eip3561.proxy.next.logic.block'))-1)&&PROPOSE_BLOCK_SLOT==bytes32(uint256(keccak256('eip3561.proxy.propose.block'))-1)&&ZERO_TRUST_PERIOD_SLOT==bytes32(uint256(keccak256('eip3561.proxy.zero.trust.period'))-1));_setAdmin(msg.sender);}modifierIfAdmin(){if(msg.sender==_admin()){_;}else{_fallback();}}function_logic()internalviewreturns(addresslogic){assembly{logic:=sload(LOGIC_SLOT)}}function_nextLogic()internalviewreturns(addressnextLogic){assembly{nextLogic:=sload(NEXT_LOGIC_SLOT)}}function_proposeBlock()internalviewreturns(uintb){assembly{b:=sload(PROPOSE_BLOCK_SLOT)}}function_nextLogicBlock()internalviewreturns(uintb){assembly{b:=sload(NEXT_LOGIC_BLOCK_SLOT)}}function_zeroTrustPeriod()internalviewreturns(uintztp){assembly{ztp:=sload(ZERO_TRUST_PERIOD_SLOT)}}function_admin()internalviewreturns(addressadm){assembly{adm:=sload(ADMIN_SLOT)}}function_setAdmin(addressnewAdm)internal{assembly{sstore(ADMIN_SLOT,newAdm)}}functionchangeAdmin(addressnewAdm)externalIfAdmin{emitAdminChanged(_admin(),newAdm);_setAdmin(newAdm);}functionupgrade(bytescalldatadata)externalIfAdmin{require(block.number>=_nextLogicBlock(),'too soon');addresslogic;assembly{logic:=sload(NEXT_LOGIC_SLOT)sstore(LOGIC_SLOT,logic)}(boolsuccess,)=logic.delegatecall(data);require(success,'failed to call');emitUpgraded(logic);}fallback()externalpayable{_fallback();}receive()externalpayable{_fallback();}function_fallback()internal{require(msg.sender!=_admin());_delegate(_logic());}functioncancelUpgrade()externalIfAdmin{addresslogic;assembly{logic:=sload(LOGIC_SLOT)sstore(NEXT_LOGIC_SLOT,logic)}emitNextLogicCanceled();}functionprolongLock(uintb)externalIfAdmin{require(b>_proposeBlock(),'can be only set higher');assembly{sstore(PROPOSE_BLOCK_SLOT,b)}emitProposingUpgradesRestrictedUntil(b,b+_zeroTrustPeriod());}functionsetZeroTrustPeriod(uintblocks)externalIfAdmin{// before this set at least once acts like a normal eip 1967 transparent proxy
uintztp;assembly{ztp:=sload(ZERO_TRUST_PERIOD_SLOT)}require(blocks>ztp,'can be only set higher');assembly{sstore(ZERO_TRUST_PERIOD_SLOT,blocks)}_updateNextBlockSlot();emitZeroTrustPeriodSet(blocks);}function_updateNextBlockSlot()internal{uintnlb=block.number+_zeroTrustPeriod();assembly{sstore(NEXT_LOGIC_BLOCK_SLOT,nlb)}}function_setNextLogic(addressnl)internal{require(block.number>=_proposeBlock(),'too soon');_updateNextBlockSlot();assembly{sstore(NEXT_LOGIC_SLOT,nl)}emitNextLogicDefined(nl,block.number+_zeroTrustPeriod());}functionproposeTo(addressnewLogic,bytescalldatadata)externalpayableIfAdmin{if(_zeroTrustPeriod()==0||_logic()==address(0)){_updateNextBlockSlot();assembly{sstore(LOGIC_SLOT,newLogic)}(boolsuccess,)=newLogic.delegatecall(data);require(success,'failed to call');emitUpgraded(newLogic);}else{_setNextLogic(newLogic);}}function_delegate(addresslogic_)internal{assembly{calldatacopy(0,0,calldatasize())letresult:=delegatecall(gas(),logic_,0,calldatasize(),0,0)returndatacopy(0,0,returndatasize())switchresultcase0{revert(0,returndatasize())}default{return(0,returndatasize())}}}}
Rationale
An argument “just don’t make such contracts upgadeable at all” fails when it comes to complex systems which do or do not heavily rely on human factor, which might manifest itself in unprecedented ways. It might be impossible to model some systems right on first try. Using decentralized governance for upgrade management coupled with EIP-1967 proxy might become a serious bottleneck for certain protocols before they mature and data is at hand.
A proxy without a time delay before an actual upgrade is obviously abusable. A time delay is probably unavoidable, even if it means that inexperienced developers might not have confidence using it. Albeit this is a downside of this EIP, it’s a critically important option to have in smart contract development today.
Security Considerations
Users must ensure that a trust-minimized proxy they interact with does not allow overflows, ideally represents the exact copy of the code in implementation example above, and also they must ensure that Zero Trust Period length is reasonable(at the very least two weeks if upgrades are usually being revealed beforehand, and in most cases at least a month).