Open code423n4 opened 1 year ago
I don't believe a MerkleMinter instance is intended to be upgradeable.
The Clones library provides a way to deploy minimal non-upgradeable proxies for cheap. This can be useful for applications that require deploying many instances of the same contract (for example one per user, or one per task). These instances are designed to be both cheap to deploy, and cheap to call. The drawback being that they are not upgradeable.
Will leave open for sponsor confirmation. But most likely this is invalid.
novaknole20 marked the issue as sponsor confirmed
It appears the sponsor does intend for this to be upgradeable since they confirmed the issue. Awarding as M
0xean marked the issue as satisfactory
Sorry we don't know why this got confirmed from our side.... As seen in the code the MerkleMinter is only used by the TokenFactory
and there we use clones (minimal non-upgradable proxies).
Therefore we need to extend from PluginUUPSUpgradeable
to disable the initializer in the base during construction.
We are aware that we cannot take back the bounty paid to the warden but we would like to have it removed from the report or a comment mentioning our mistake in accepting it and that we never attend to use it in an upgradeable pattern.
In Discord we discussed adding additional wording/context to the finding M-02:
MerkleMinter
is UUPSUpgradeable
and the upgradeability is part of its feature set. However, in the context of the TokenFactory
we opted to deploy it via the minimal proxy pattern to save gas. This is because its upgradeability feature is not needed here and the cloning can be done as well. We are fully aware that the MerkleMinter
proxies created through the TokenFactory
are not upgradeable.
Although we accidentally accepted this medium risk finding initially, we don't think that intentional absent of upgradeability qualifies as medium risk/finding. Accordingly, we would suggest to not rate this as a medium risk finding in the final audit report.
To make our intentions more clear, we made the following change to the documentation: https://github.com/aragon/osx/pull/362/files
It is also worth noting that we are currently using neither TokenFactory
nor MerkleMinter
/MerkleDistributor
in our framework and that we will move the code out of the aragon/osx repository in the future.
warden got that $17k tho?
Lines of code
https://github.com/code-423n4/2023-03-aragon/blob/main/packages/contracts/src/framework/utils/TokenFactory.sol#L119-L125
Vulnerability details
Impact
During the token creation process in the
TokenFactory
contract, the function creates aMerkleMinter
contract to setup and handle token initial token distribution.https://github.com/code-423n4/2023-03-aragon/blob/main/packages/contracts/src/framework/utils/TokenFactory.sol#L119-L125
The
MerkleMinter
contract is an upgradeable contract, as it inherits fromPluginUUPSUpgradeable
:https://github.com/code-423n4/2023-03-aragon/blob/main/packages/contracts/src/plugins/token/MerkleMinter.sol#L20
However, as we can see in the first code snippet, the
MerkleMinter
instance created increateToken
is a cloned instance (using OpenZeppelinClones
library). This is incompatible with upgradeable contracts, which require the use of a proxy.This issue will cause the
MerkleMinter
instance created throughTokenFactory
to fail to be upgraded. TheMerkleMinter
contract will contain all the required logic to be upgraded, but the action will fail as there is no proxy to change to a new potential implementation.Proof of Concept
The following test illustrates the issue. We call
createToken
to get an instance ofMerkleMinter
. We then simulate a new version of the contract to upgrade to (merkleMinterV2Impl
) and try to upgrade theMerkleMinter
instance to this new implementation. The call fails with a "Function must be called through active proxy" error (error is defined in OpenZeppelin baseUUPSUpgradeable
contract).Note: the snippet shows only the relevant code for the test. Full test file can be found here.
Recommendation
The
MerkleMinter
instance should be created using a proxy over the base implementation (createERC1967Proxy
) instead of cloning the implementation: