Proxy
Last updated
Was this helpful?
Last updated
Was this helpful?
The EVM Fulfillment Protocol uses the Universal Upgradeable Proxy Standard (UUPS) pattern to enable contract upgradeability. This approach allows the protocol to evolve while preserving contract state and addresses.
In the UUPS pattern, the upgrade functionality is implemented in the implementation contract itself, rather than in the proxy. This reduces the proxy contract size and gas costs while maintaining the ability to upgrade the implementation.
The proxy architecture in the EVM Fulfillment Protocol can be broken down into the following concepts:
All proxy contracts in the EVM Fulfillment Protocol extend OpenZeppelin's ERC1967Proxy
, which implements the ERC-1967 standard for proxy storage slots. This ensures compatibility with tools and standards in the Ethereum ecosystem.
The proxy contracts are minimal by design, containing only the constructor that sets up the initial implementation address and initialization data. All other functionality is delegated to the implementation contract.
The implementation contracts inherit from OpenZeppelin's UUPSUpgradeable
contract, which provides the upgrade functionality. The key components of this mechanism are:
upgradeTo
Function: Allows upgrading to a new implementation address.
upgradeToAndCall
Function: Allows upgrading to a new implementation and calling a function in the same transaction.
_authorizeUpgrade
Function: Provides access control for upgrades, typically restricted to the contract owner.
Since constructors are not used in upgradeable contracts (as they're only run once during deployment), the protocol uses initializer functions to set up the initial state:
The initializer
modifier ensures that the initialization function can only be called once, preventing re-initialization attacks.
The EVM Fulfillment Protocol uses a versioned inheritance pattern for its implementation contracts:
This approach allows for:
Incremental Upgrades: New versions can build upon previous versions, inheriting their functionality.
Clear Version History: The inheritance chain provides a clear history of contract versions.
Backward Compatibility: New versions maintain the same interface as previous versions, ensuring compatibility.
The deployment process for a proxy and its implementation follows these steps:
Deploy Implementation: Deploy the implementation contract without initialization.
Prepare Initialization Data: Encode the initialization function call with its parameters.
Deploy Proxy: Deploy the proxy contract, passing the implementation address and initialization data.
Verify Deployment: Confirm that the proxy is correctly delegating to the implementation.
To upgrade a contract in the EVM Fulfillment Protocol:
Deploy New Implementation: Deploy the new implementation contract.
Call upgradeTo
: Call the upgradeTo
function on the current implementation through the proxy, passing the new implementation address.
Verification: Verify that the proxy is now delegating to the new implementation.
All upgrade functions in the protocol are protected by the onlyOwner
modifier, ensuring that only the contract owner can perform upgrades.
When upgrading implementation contracts, it's critical to maintain the same storage layout to prevent data corruption. The protocol follows these practices:
Append-Only Storage: New storage variables are only added at the end of the contract.
No Storage Removal: Existing storage variables are never removed, only deprecated.
Type Preservation: The types of storage variables are never changed.
Before upgrading to a new implementation, the protocol ensures that:
The new implementation is compatible with the existing storage layout.
The new implementation has been thoroughly tested.
The new implementation has undergone security audits when appropriate.
All initialization functions use the initializer
modifier to prevent re-initialization attacks.