#Will BTC Hit a New High?#
BTC has broken past $100,000 and is now consolidating near its peak. What’s your outlook on the next move? With bullish sentiment on the rise, could BTC reach a new all-time high?
#Crypto Market Rebounds#
The crypto market is rallying across the board — altcoins are gaining momentum, and Memecoins are heating up! 🔥MOODENG is up over 100%, while PNUT and VIRTUAL have each gained more than 45%. The total crypto market cap has now exceeded $3.22 trillion!
What’s next for the market? Which rebound tokens are you most bullish on?
Post your insights and trading strategie
Breaking the immutability of the blockchain: How the proxy model can achieve smart contract upgrades
The proxy pattern enables smart contracts to upgrade their logic while maintaining their on-chain addresses and state values. The call to the proxy contract will execute the code from the logic contract through delegateCall to modify the state of the proxy contract.
This article will provide an overview of the types of proxy contracts, related security incidents and recommendations, and best practices for using proxy contracts.
Introduction to Upgradable Contracts and Proxy Mode
We all know the "non-tamperable" feature of the blockchain, and the smart contract code cannot be modified after being deployed on the blockchain.
So when developers want to update the contract code for logic upgrades, bug fixes, or security updates, they must deploy a new contract, and a new contract address will be generated.
To solve this problem, you can use the proxy mode.
The proxy mode realizes the upgradeability of the contract without changing the deployment address of the contract, which is currently the most common contract upgrade mode.
Proxy mode is an upgradable contract system, including proxy contract and logic implementation contract.
The proxy contract handles user interaction and data and contract state storage. The user's call to the proxy contract will execute the code from the logic contract through delegatecall(), thereby changing the state of the proxy contract. The upgrade is realized by updating the logical contract address recorded in the proxy contract predetermined storage slot.
The three more conventional proxy modes are transparent proxy, UUPS proxy and Beacon proxy.
Transparent Proxy
In the transparent proxy mode, the upgrade function is implemented in the proxy contract. The administrator role of the proxy contract is given the direct authority to operate the proxy contract to update the logical implementation address corresponding to the proxy. Callers without admin privileges will delegate their calls to the implementing contract.
Note: The proxy contract administrator cannot be a key role in the logical implementation of the contract, nor can it even be an ordinary user, because the proxy administrator cannot interact with the implementation contract.
UUPS Proxy
In the UUPS (Universal Upgradeable Proxy Standard) mode, the contract upgrade function is implemented in the logic contract. Since the upgrade mechanism is stored in the logic contract, the upgraded version can delete the upgrade-related logic to prohibit future upgrades. In this mode, all calls to the proxy contract are forwarded to the logic implementation contract.
Beacon Proxy
The Beacon proxy mode allows multiple proxy contracts to share the same logic implementation by referencing the Beacon contract. The Beacon contract provides the address of the logic implementation contract for the called proxy contract. When upgrading to a new logic implementation address, only the address recorded in the Beacon contract needs to be updated.
Proxy Misuse and Security Incidents
Developers can utilize proxy mode contracts to implement upgradable contract systems. However, the proxy mode also has certain operational thresholds. If used improperly, it may bring devastating security problems to the project. The following sections showcase incidents related to proxy misuse, and the centralization risks that proxies pose.
Proxy Managed Key Disclosure
The proxy administrator controls the upgrade mechanism of the transparent proxy mode, if the administrator's private key is leaked, the attacker can upgrade the logic contract and execute their own malicious logic on the proxy state.
On March 5, 2021, PAID Network suffered a "minting" attack caused by poor private key management. The PAID Network was exploited by an attacker who stole the proxy administrator's private key and triggered an upgrade mechanism to change the logic contract.
After the upgrade, the attacker can destroy the user's PAID and mint a batch of PAID for himself, which can be sold later. There is no security vulnerability in the code itself, but the attacker obtained the private key to upgrade the contract from the administrator.
** Uninitialized UUPS proxy implementation **
For the UUPS proxy mode, during the initialization of the proxy contract, the initial parameters are passed to the proxy contract by the caller, and then the proxy contract calls the initialize() function in the logic contract to achieve initialization.
The initialize() function is usually protected with the "initializer" modifier to restrict the function to be called only once. After calling the initialize() function, from the perspective of the proxy contract, the logic contract is initialized.
However, from the perspective of the logic contract, the logic contract is not initialized because initialize() is not called directly in the logic contract. Given that the logic contract itself is not initialized, anyone can call the initialize() function to initialize it, set the state variable to a malicious value, and potentially take over the logic contract.
The impact of a logic contract being taken over depends on the contract code in the system. In the worst case, an attacker can upgrade the logic contract in the UUPS proxy mode to a malicious contract and execute a "self-destruct" function call, which may cause the entire proxy contract to become useless, and the assets in the contract will be permanently destroyed lost.
case
① Parity Multisig Freeze: The logic contract is not initialized. The attacker triggers the initialization of many wallets and locks ether in the contract by calling selfdestruct().
② Harvest Finance, Teller, KeeperDAO, and Rivermen all use uninitialized logic contracts, which will allow attackers to set the initialization parameters of the contracts arbitrarily, and execute selfdestruct() during delegatecall() to destroy the proxy contract.
Storage Conflict
In an upgradable contract system, the proxy contract does not declare state variables, but uses pseudo-random storage slots to store important data.
Proxy contracts store the values of logical contract state variables relative to where they were declared. If the proxy contract declares its own state variables and both the proxy and the logic contract try to use the same storage slot, a storage conflict will occur.
The proxy contract provided by the OpenZeppelin library does not declare state variables in the contract, but based on the EIP 1967 standard, saves the value that needs to be stored (such as the management address) in a specific storage slot to prevent conflicts.
case
On July 23, 2022, Beijing time, the decentralized music platform Audius was hacked. The incident was caused by the introduction of new logic in the proxy contract, resulting in storage conflicts.
The proxy contract declares a proxyAdmin address state variable, and its value will be read incorrectly when the logic contract code is executed.
The value of proxyAdmin customized by the project party was mistakenly regarded as the value of initialized and initialized, so that the initializer modifier returned a wrong result, which allowed the attacker to call the initialize() function again and grant himself the authority to manage the contract. The attackers then changed the voting parameters and passed their malicious proposal in order to steal Audius assets.
Call delegatecall() in logic contract or untrusted contract
Suppose delegatecall() exists in a logical contract, and the contract does not properly validate the target of the call. In this case, an attacker can exploit this function to execute calls to malicious contracts they control, to subvert logic implementations, or to execute custom logic.
Similarly, if there is an unrestricted address.call() function in the logic contract, once the attacker maliciously provides the address and data fields, it can be used as a proxy contract.
case
Pickle Finance, Furucombo, and dYdX attacks.
In these incidents, the vulnerable contract was approved by the user token, and there is a call()/delegatecall() in the contract that is provided by the user to call the contract address and data, the attacker will be able to call the transferFrom() function contract to withdraw user balances. During the dYdX incident, dYdX performed their own white hat attack to protect funds.
Best Practices
generally
(1) Use proxy mode only when necessary
Not every contract needs to be upgraded. As shown above, there are many risks involved in using the proxy pattern. The "upgradable" property also raises trust issues, as proxy administrators can upgrade contracts without the consent of the community. We recommend integrating the proxy pattern into projects only when necessary.
(2) Do not modify the proxy library
The proxy contract library is complex, especially the part that deals with storage management and upgrade mechanisms. Any errors in the modification will affect the work of the proxy and logic contracts. A large number of high-severity agent-related bugs that we found during our audits were caused by incorrect agent library modifications. The Audius incident is a prime example of the consequences of improper modification of agency contracts.
Key Points of Agency Contract Operation and Management
(1) Initialize the logic contract
An attacker can take over an uninitialized logic contract and potentially compromise the proxy contract system. So please initialize the logic contract after deployment, or use _disableInitializers() in the constructor of the logic contract to automatically disable initialization.
(2) Ensure the security of the agent management account
An upgradable contract system usually requires a privileged role of "proxy administrator" to manage contract upgrades. If the management key is leaked, the attacker can freely upgrade the contract to a malicious contract, which can steal users' assets. We recommend careful management of the private keys of proxy admin accounts to avoid any potential risk of hacking. Multi-signature wallets can be used to prevent single-point key management failures.
(3) Use a separate account for transparent proxy management
Proxy management and logic governance should be separate addresses to prevent loss of interaction with the logic implementation. If proxy management and logical governance refer to the same address, no calls are forwarded to perform privileged functions, thus prohibiting changes to governance functions.
Proxy contract storage related
(1) Be careful when declaring state variables in proxy contracts
As explained in the Audius hack, proxy contracts must be careful when declaring their own state variables. State variables declared in the normal way in proxy contracts can cause data conflicts when reading and writing data. If the proxy contract requires a state variable, save the value in a storage slot like EIP1967 to prevent conflicts when executing logic contract code.
(2) Maintain the variable declaration order and type of the logic contract
Each version of the logic contract must maintain the same order and type of state variables, and new state variables need to be added at the end of existing variables. Otherwise, delegate calls can cause proxy contracts to read or overwrite incorrect stored values, and old data may be associated with newly declared variables, which can cause serious problems for applications.
(3) Include storage gaps in the base contract
Logic contracts need to include storage gaps in the contract code to anticipate new state variables when deploying new logic implementations. After adding a new state variable, the size of the gap needs to be updated appropriately.
(4) Do not set the value of the state variable in the constructor or declaration process
Assigning a state variable during declaration or in the constructor only affects the value in the logic contract, not the proxy contract. Non-immutable parameters should be assigned using the initialize() function.
Contract Inheritance
(1) Upgradable contracts can only inherit from other upgradable contracts
Upgradable contracts have a different structure than non-upgradeable contracts. For example, the constructor is not compatible with changing the agent state, it uses the initialize() function to set state variables.
Any contract that inherits from another contract needs to use the initialize() function of its inherited contract to assign its respective variables. When using the OpenZeppelin library or writing your own code, ensure that upgradable contracts can only inherit other upgradable contracts.
(2) Do not instantiate new contracts in logic contracts
Contracts created and instantiated through Solidity will not be upgradeable. Contracts should be deployed individually and pass their address as a parameter to the upgradable logic contract to achieve an upgradable state.
(3) Parent contract initialization risk
When initializing the Parent contract, the __{ContractName}_init function will initialize its Parent contract. Multiple calls to __{ContractName}_init may result in a second initialization of the Parent contract. Note that __{ContractName}_init_unchained() will only initialize the parameters of {ContractName}, and will not call the initializer of its Parent contract.
However, this is not a recommended practice, because all Parent contracts need to be initialized, and not initializing the required contracts will cause future execution problems.
Implementation of logic contract
Avoid selfdestruct() or selegatecall()/call() to untrusted contracts
If there is selfdestruct() or delegatecall() in the contract, it is possible for an attacker to use these functions to break the logic implementation or execute custom logic. Developers should validate user input and not allow contracts to execute delegatecalls/calls to untrusted contracts. Also, it is not recommended to use delegatecall() in logic contracts because it would be cumbersome to manage the storage layout in the delegate chain of multiple contracts.
Written at the end
Proxy contracts bypass the immutable nature of blockchains by enabling protocols to update their code logic after deployment. However, the development of proxy contracts still needs to be very careful, and incorrect implementation may cause project security and logic problems.
Overall, the best practice is to use authoritative and extensively tested solutions, as Transparent, UUPS, and Beacon Proxy modes each have proven upgrade mechanisms for their respective use cases. In addition to this, privileged roles for escalating agents should also be securely managed to prevent attackers from altering agent logic.
The logic implementation contract should also be careful not to use delegatecall(), which can prevent attackers from executing some malicious code, such as selfdestruct().
While following best practices ensures stable proxy contract deployments while maintaining upgradable flexibility, all code is prone to new security or logic issues that could jeopardize the project. Therefore, all code is best audited by a team of security experts with experience in auditing and securing proxy contract protocols.