Full Report
The blockchain is immutable. However, we don't necessarily want our code to be immutable, since we like to be able to fix it up. So, this is done with proxies - a contract that sits in front of our implementation, which is accessed with a delegateCall() to share storage. There is a problem though: the constructor only runs at the deployment of the contract. So, what if we want to have similar logic then we are going to need to write this ourselves with the same constraints as a constructor. In Open Zeppelin, this is called the Initializer pattern. Open Zeppelin implements the initializer() modifier for us. This will allow the initialize function to be called once and only once during the lifespan of the proxy. Although the initializer() is implemented with the functionality, this will not automatically call the children constructors like a real constructor will do. So, we must do that manually. To prevent these from being called by ours, we can use the onlyInitializing() modifier. There is another function called reinitializer() that can be used to allow for initializations after the initial one. There are a few no-nos within these because of Solidity internal restrictions. First, it's a bad idea to set static values within field declarations. Why? This is equivalent to setting it within the constructor so it doesn't work. To solve this issue, use a storage slot instead. Immutables and constants are fine to upgrade since these are actually stored in the bytecode used but great care should be taken while doing this. Second, the implementation contract needs to disable initialization patterns. This is because we don't want an attacker to initialize the contract, have a delegateCall() occur to trigger a self-destruct. Storage changes must be done very carefully as well. Changing slots, types and whatever else is absolutely terrifying, since the code semantic meaning can change. Overall, an interesting pattern that is necessary for proxies that should be scrutinized carefully.
Analysis Summary
# Best Practices: Secure Proxy Contract Initialization
## Overview
These practices address the security risks associated with **Upgradeability Patterns** in Ethereum smart contracts. Because standard constructors do not work with Proxy patterns (which use `delegatecall`), developers must use "Initializers." Failure to secure these initializers can lead to contract hijacking, storage collisions, or permanent destruction of the implementation contract.
## Key Recommendations
### Immediate Actions
1. **Disable Implementation Initializers:** Explicitly call `_disableInitializers()` in the constructor of your implementation (logic) contract to prevent attackers from taking ownership of it.
2. **Apply Initializer Modifiers:** Use the OpenZeppelin `initializer` modifier on your main initialization function to ensure it can only be executed once.
3. **Manual Parent Initialization:** Manually call the initialization functions of all inherited parent contracts, as Solidity's automatic constructor chaining does not apply to initializer functions.
### Short-term Improvements (1-3 months)
1. **Migrate Field Declarations:** Audit all state variables. Move values set in field declarations (e.g., `uint256 public x = 10;`) into the initializer function or a storage slot, as field declarations are ignored by proxies.
2. **Storage Layout Audit:** Implement strict storage layout monitoring. Avoid changing the order, type, or names of existing state variables during upgrades to prevent storage collisions.
### Long-term Strategy (3+ months)
1. **Formal Verification of Upgrades:** Establish a pipeline for checking storage layout compatibility between contract versions using tools like the OpenZeppelin Upgrades Plugin.
2. **Transition to Immutable/Constant Usage:** Where possible, use `constant` or `immutable` variables for values that do not change, as these are stored in bytecode and are safer during upgrades than standard storage slots.
## Implementation Guidance
### For Small Organizations
- Use established libraries like **OpenZeppelin Contracts Upgradeable** rather than writing custom proxy logic.
- Conduct peer reviews specifically focusing on the `initializer` and `onlyInitializing` modifiers.
### For Medium Organizations
- Integrate **automated storage layout checkers** into the CI/CD pipeline to catch accidental storage slot shifts before deployment.
- Implement a Multi-Sig requirement for executing the `reinitializer` function for contract upgrades.
### For Large Enterprises
- Require **formal audits** for every implementation contract upgrade.
- Maintain a comprehensive "Storage Gap" strategy in base contracts to allow for future variable additions without disrupting the layout.
## Configuration Examples
### Correct Initializer Pattern
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
contract MyContract is Initializable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
// Immediate Action: Prevent implementation contract from being initialized
_disableInitializers();
}
function initialize(uint256 _data) public initializer {
// Manual call to parent initializers would go here
__MyParent_init();
myValue = _data;
}
}
## Compliance Alignment
- **SWC Registry (Smart Contract Weakness Classification):** Directly addresses SWC-118 (Incorrect Constructor Name) and risks related to `delegatecall` vulnerabilities.
- **NIST IR 8401:** Aligns with foundational security activities for blockchain-based systems regarding code integrity and lifecycle management.
## Common Pitfalls to Avoid
- **Setting Values in Declarations:** Do not write `uint256 public value = 1;` outside of a function; the proxy will see this as `0`.
- **Unprotected Implementation:** Leaving the logic contract uninitialized allows an attacker to call `initialize()` and potentially trigger a `selfdestruct` via `delegatecall`.
- **Reordering Variables:** Changing the order of variables in a new version will cause the proxy to read the wrong data from storage slots.
## Resources
- **OpenZeppelin Upgrades Documentation:** hxxps://docs.openzeppelin[.]com/upgrades-plugins/
- **Solidity Documentation (Delegatecall):** hxxps://docs.soliditylang[.]org/en/latest/introduction-to-smart-contracts.html#delegatecall-callcode-and-libraries
- **SWC Registry:** hxxps://swcregistry[.]io/