Closed code423n4 closed 2 years ago
When matured fCash components are redeemed to the underlying token, the underlying token should be added as a component via the _updateSetTokenPositions
method in _redeemFCashPosition
. So the underlying token does not have to be a set component before that redemption.
I don't see any reasoning / evidence here that points to this mechanism not working.
Lines of code
https://github.com/code-423n4/2022-06-notional-coop/blob/6f8c325f604e2576e2fe257b6b57892ca181509a/index-coop-notional-trade-module/contracts/protocol/modules/v1/NotionalTradeModule.sol#L156 https://github.com/code-423n4/2022-06-notional-coop/blob/6f8c325f604e2576e2fe257b6b57892ca181509a/index-coop-notional-trade-module/contracts/protocol/modules/v1/NotionalTradeModule.sol#L185 https://github.com/code-423n4/2022-06-notional-coop/blob/6f8c325f604e2576e2fe257b6b57892ca181509a/index-coop-notional-trade-module/contracts/protocol/modules/v1/NotionalTradeModule.sol#L294
Vulnerability details
Proof-of-Concept
Within the
NotionalTradeModule.mintFCashPosition
function, it will check if the_sendToken
submitted is a component of the SetToken before the minting process. Within theNotionalTradeModule.redeemFCashPosition
function, it will check if thewrappedfCash
is is a component of the SetToken before the redemption.By default, when a fCash (e.g. Wrapped fDAI @ 10 October 2022) is redeemed, it will redeem to an asset token (e.g. cDAI). In most cases, a manager will mint fCash by calling
NotionalTradeModule.mintFCashPosition
with an asset token (e.g. cDAI) as the_sendToken
. Thus, the asset token (e.g. cDAI) will be added to the components of the SetToken because if the manager did not do so, the minting process will revert due to therequire(_setToken.isComponent(address(_sendToken)), "Send token must be an index component");
https://github.com/code-423n4/2022-06-notional-coop/blob/6f8c325f604e2576e2fe257b6b57892ca181509a/index-coop-notional-trade-module/contracts/protocol/modules/v1/NotionalTradeModule.sol#L156
https://github.com/code-423n4/2022-06-notional-coop/blob/6f8c325f604e2576e2fe257b6b57892ca181509a/index-coop-notional-trade-module/contracts/protocol/modules/v1/NotionalTradeModule.sol#L185
A manager could configure a SetToken to always redeem to underlying tokens upon reaching maturity by calling the
NotionalTradeModule.setRedeemToUnderlying
function.https://github.com/code-423n4/2022-06-notional-coop/blob/6f8c325f604e2576e2fe257b6b57892ca181509a/index-coop-notional-trade-module/contracts/protocol/modules/v1/NotionalTradeModule.sol#L294
Assume that the manager has just minted 100 "Wrapped fDAI @ 10 October 2022" tokens by calling
NotionalTradeModule.mintFCashPosition
with an asset token (e.g. cDAI) as the_sendToken
. After minting, the manager decided to configure the SetToken to always redeem to underlying token instead of asset token upon reaching maturity. Thus, the fCash will always be redeemed to its underlying token, which isDAI
in this example.It was observed that throughout the process, the system did not enforce that the underlying token (
DAI
) must be an index component (in other wordDAI
must be a component of the SetToken). Thus, it is possible to reach a state where an underlying token (e.g.DAI
) exists in theSetToken
contract, but it is not a component of the SetToken.Impact
If this happens, following issue will occur:
NotionalTradeModule.redeemMaturedPositions
function is triggered to redeem matured fCash positions, the redeemed underlying token will be sent to the SetToken Contract.DAI
is not in the components of the SetToken, user will never get hisDAI
during redemption. As users already burned their SetToken during redemption, so there is no way for them to get back their share ofDAI
even if the manager realises and fixes this issue at a later date by addingDAI
to the components.Recommended Mitigation Steps
To eliminate such an event from happening, it is recommended to implement additional validation check to ensure that the system will never reach a state where an underlying token (e.g.
DAI
) exists in theSetToken
contract, but it is not a component of the SetToken.Without this control in place, it is likely that this issue will happen as no one can ensure that all managers that utilise the
NotionalTradeModule
module will know that they are required to add the underlying token to components of the SetToken before callingNotionalTradeModule.setRedeemToUnderlying
function.Additionally, human-error might happen and they might forget to perform this step. Thus, it is essential for the protocol to implement the necessary controls to prevent their manager from falling into this pitfall.
Considering implement additional checks as shown below: