👮
Contract Cops
  • Mastering Ethereum Book
    • What is ethereum?
    • Tokens
    • Oracles
    • Decenralized Applications(DApps)
    • The Ethereum virtual machine
    • Ethereum basics
    • Ethereum clients
    • Cryptography
    • Wallets
    • Transactions
    • Chapter 7 - Smart Contracts & Solidity
    • Side Notes
      • Tokens
      • Smart Contracts and Solidity
  • Cryptography
    • Ethereum Cryptography - Cheatsheet
    • Assymetric vs symmetric cryptography
    • ECDSA vs RSA
    • Elliptic curves and ECDSA
    • Sha-256 Example
    • Sha-256
    • What are the different steps in SHA-256?
  • Ethereum Blocks
    • Block Headers
  • Learning Solidity
    • Storage vs memory
    • Upgradeable contracts
      • Proxy pattern in smart contracts
  • PoS
    • Proof of stake
  • PoW
    • PoW
  • Tokens
    • ERC-1155
    • ERC20
  • Cryptonomics
    • Automated market makers
    • Collateral Tokens
    • Collateralized Stablecoin
    • Fiat currency
    • Liquidity pool
    • Open Position: Meaning and Risk in Trading
    • Slippage
    • Spot price
  • Common Attack Vectors
    • Checking access control
    • Access control issues on critical functions
    • Account Existence Check for low level calls
    • Account Existence Check
    • Common attacks with contract/EOA addresses
    • Arithmetic under/overflow
    • Assert Attack
    • Assert require revert
    • Assert Violation
    • Bad Interface DOS
    • Bad pragma and compiler
    • Block Timestamp Manipulation
    • Bypassing contract check
    • Code With No Effects
    • Code size check vulnerability
    • Constructors with Care
    • Default Visibilities
    • Delegatecall
    • Delegatecall
    • Denial of Service (DoS)
    • DoS with block gas limit
    • Entropy Illusion
    • External contract referencing
    • Flash Loan Attack
    • Floating Point and Precision
    • Function selector abuse
    • Function selector abuse
    • Smart contract gas griefing
    • Hash collision parameters
    • Hash Collisions With Multiple Variable Length Arguments
    • Imprecise arithmetic
    • Improper Array Deletion
    • Incorrect array deletion
    • Incorrect interface
    • Insufficient Gas Griefing
    • Loop through long arrays
    • Message call with hardcoded gas amount
    • Not enough gas for ether transfer
    • Precision Loss in Calculations
    • Oracle Manipulation
    • Public Burn Function
    • Read-only reentrancy
    • Race Conditions/Front Running
    • Reentrancy Attacks
    • Reentrancy
    • Requirement Violation
    • Right-To-Left-Override control character (U+202E)
    • Shadowing State Variables
    • Short Address / Parameter attack
    • Signature Malleability
    • Signature Replay
    • Transaction Order Dependence
    • Tx.Origin Authentication
    • Unchecked CALL Return Values
    • Unexpected ether
    • Uninitialized Storage Pointers
    • Unsafe Ownership Transfer
  • EIP's
    • EIP155
    • EIP55
  • PoW
    • Ethash
    • Scrypt - RFC 7914
  • Questions for self evaluation
    • Questions 23/04/2023 (Nr: 84)
    • Usability guide for questions
  • Frequently asked questions
    • What is the difference between transaction and message?
    • What is the use of a interface or function without implementation?
  • UsefulResources
Powered by GitBook
On this page
  1. Learning Solidity
  2. Upgradeable contracts

Proxy pattern in smart contracts

PreviousUpgradeable contractsNextPoS

Last updated 2 years ago

The proxy pattern (upgradeable smart contracts) is a pattern in Solidity programming that allows contracts to be written in such a way that if a bug is discovered in the future, the contract can be "upgraded", while the storage stays the same.

  • Smart contracts are immutable, which means that once they are deployed onto the Ethereum blockchain, there is no viable way to change them. The only programmable option to directly alter a deployed contract is by using the SELFDESCTRUCT opcode which allows the contract to be removed from the blockchain.

The proxy pattern should be used for potenially discovering bugs and being able to fix them/breaking changes with dependencies or specific OPCODES that are taken out of Solidity versions.

  • Important to note that proxy/upgradeable contracts are not able to alter the originally deployed contract in any way. A use case would be to have a modifiable address state variable that points to a library contract.

The flow of the proxy pattern can be seen in the following diagram:

  • Proxy contracts can also be recognized as dispatchers that forward a transaction/call to a different module for its logic implementation.

Proxy - storage and contextThe storage variables of upgradeable contracts can't be deleted/ommited (immutable), however, new storage variables might be added.Important note: delegate contracts (module contracts) must have the same storage layout as the proxy contract. Changing this/ommiting this could cause a catastrophic fault/change in the state variables of the original proxy contract.As for the context of a transaction, the following rules remain true:msg.sender is always the external contract/EOA that called the proxy, which in terms delegatecalled the logic/delegate contract. Therefore, once the transaction reaches the delegate, msg.sender would NOT be the proxy contract.msg.value is also preserved, therefore any ether/bytes that is sent ends up at the delegate contractstorage variables of the proxy are altered in the delegate contractHow is the proxy pattern implemented in SolidityIn Solidity (and other programming languages for that matter), modularity is a common practice that allows the seperation of business logic. Since smart contracts are immutable, we have to get creative in order to make a contract extendable.In essence, the implementation of some business logic is done by a delegate contract. The address of this delegate contract is usually stored in a modifiable state variable inside the original proxy smart contract.Ultimatelly, we want to recieve some data in the proxy contract and without modifying it - forward it to one of the proxy contracts that will then perform the necessary operations on it.Pre-requisitesIn order to best understand how proxies delegate information to a different contract, we need to understand how Solidity deals with "call data" to an external contract.In Solidity, we are able to perform an external call to a contract with the following snippet:(bool success,) = someContractAddress.call{value:123}(abi.encodeWithSignature("test(uint256)", 123))With the above snippet of code, we are doing the following:Externally calling someContractAddress, with a specified value of 123 (wei) and the encoded function specifier, pointing to test(uint256) and a value of 123Delegatecall has almost the exact same syntax, with the important difference of the preservation rules (mentioned above in this notebook).(bool success,) = someContractAddress.delegatecall{value:123}(abi.encodeWithSignature("test(uint256)", 123))Now, given all of this, it is not trivial to come to the conclusion that if we can somehow change the "someContractAddress" and the encoded call data that is being sent, we can achieve modularity and extend our proxy contract.ImplementationIt is important to note that delegatecall only returns a boolean of whether or not the function was succesfull or not. In order to overcome this, we can use some simple inline assembly in order to recieve further data from using a delegate contract.To start things off, we look at Solidity's fallback function. The way that the EVM handles things in Solidity is that when msg.data of a transaction contains a function specifier that is not contained in a contract, it is forwarded to the fallback function of the called contract.fallback() external { assembly { let ptr: = mload(0x40) calldatacopy(ptr, 0, calldatasize()) let result: = delegatecall( gas(), sload(implementation.slot), ptr, calldatasize(), 0, 0 ) let size: = returndatasize() returndatacopy(ptr, 0, size) switch result case 0 { revert(ptr, size) } default { return (ptr, size) } }}We use the above shown inline assembly in order to be able to recieve any kind of return output from the function we are delegatecalling to. Inline assembly is necessary here, as we do not know what kind of data will be returned in advance.As explained in , we are able to bypass the need to use assembly if the delegate contract uses an event to broadcast the result. Since events cannot be listened to by smart contract themselves, the result would have to be directly interpreted by some frontend/something external of the Ethereum blockchain.

Proxy pattern and upgradeable smart contracts