The current implementation incorrectly places the storage variables in the logic contract (the core/
files, such as the TokenManager.sol
) and its parent contracts (the storage/
files, such as TokenManagerStorage
) instead of in the proxy contract.
This is a fundamental misunderstanding of how upgradeable proxy patterns should work.
In the TokenManager
contract (and its parent TokenManagerStorage
):
These storage variables are defined in the logic contract, which will be replaced during an upgrade, resulting in loss of all stored data.
In a proper upgradeable proxy pattern:
The proxy contract should hold the state (storage variables).
The logic contract should be stateless and only contain the logic to manipulate the state.
The proxy delegates all calls to the logic contract, which reads and writes state as if it were its own, but the state actually resides in the proxy.
The current implementation not only fails to leverage the upgradeable design but also guarantees complete state loss upon any upgrade, as the new logic contract would start with fresh, uninitialized storage.
Add the following test in PreMarkets.t.sol
. Be sure to bring the necessary imports and modify the variable initializations.
This proof of code illustrates the critical issue of state loss when storage variables are defined in the logic contract instead of the proxy. The example uses the wrappedNativeToken
, but the same issue applies to other variables like userTokenBalanceMap
.
Guaranteed State Loss: Every upgrade will result in complete loss of all stored data, as the new logic contract will have its own, uninitialized storage.
Broken Functionality: After an upgrade, all functions that rely on previously stored data will either revert or operate on default values, potentially leading to critical failures or unexpected behaviors.
Impossible Upgrades: It's effectively impossible to perform a meaningful upgrade without losing all existing data, making the entire upgrade mechanism futile.
Potential Fund Lock: If the contract handles any assets or critical permissions based on stored data, these could become permanently inaccessible after an upgrade.
Manual Review
Redesign the upgrade mechanism to follow the standard proxy pattern:
Move all storage variables to the proxy contract.
Make the logic contract stateless, only containing functions to manipulate state.
Ensure the proxy contract uses delegatecall
to forward calls to the logic contract.
Implement a proper storage layout in the proxy.
3. Modify the logic contract to be stateless.
4. Update the TadleFactory to work with this new design, ensuring it interacts correctly with the proxy and logic contracts.
5. Implement thorough testing of the upgrade process to ensure state preservation and correct functionality after upgrades.
By implementing these changes, the system can achieve true upgradeability, preserving state across upgrades and maintaining system consistency.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.