The module is expected to have no state changes in case a swap failed, and continue to the conversion phase. It was implemented by swallowing the error with a log and continuing with the flow (erc20 conversion, etc). This is the relevant code section:
In reality, the TradeInputForExactOutput function might change the store state and the caller will not revert it in case of an error.
For example, the internal swapCoins function calls SendCoins twice. In case the first one succeeded and the second failed and returned an error, the users will lose the in funds (which will be sent to the pool) and will not receive the out funds, leading to direct fund loss.
When swallowing errors this way, the state should be reverted to what it was right before the operation started, using a cached context.
Proof of Concept
Apply this patch. It will cause swap to error after some state changes happened (one side of the swap):
Add this test to x/onboarding/keeper/ibc_callbacks_test.go :
{
"swap fails after state change / convert remaining ibc token - some ibc tokens are lost to the pool, the rest are not converted",
func() {
transferAmount = sdk.NewIntWithDecimal(25, 6)
transfer := transfertypes.NewFungibleTokenPacketData(denom, transferAmount.String(), secpAddrCosmos, ethsecpAddrcanto)
bz := transfertypes.ModuleCdc.MustMarshalJSON(&transfer)
packet = channeltypes.NewPacket(bz, 100, transfertypes.PortID, sourceChannel, transfertypes.PortID, cantoChannel, timeoutHeight, 0)
},
true,
sdk.NewCoins(sdk.NewCoin("acanto", sdk.NewIntWithDecimal(3, 18))),
sdk.NewCoin("acanto", sdk.NewIntWithDecimal(3, 18)),
sdk.NewCoin(uusdcIbcdenom, sdk.NewIntFromUint64(20998399)),
sdk.NewInt(0),
},
It will pass, and we can see that the token conversion failed as well (because the math of the remaining tokens to transfer from the escrow address was too high).
It means that (artificial!) swap failure can potentially cause loss of funds and disable the conversion.
Tools Used
IDE.
Recommended Mitigation Steps
A cached context should be used, and the store changes should be committed only in case the TradeInputForExactOutput call returned no error.
One good example is Osmosis's cached context wrapper that can be found here https://github.com/osmosis-labs/osmosis/blob/main/osmoutils/cache_ctx.go .
Lines of code
https://github.com/code-423n4/2023-06-canto/blob/main/Canto/x/onboarding/keeper/ibc_callbacks.go#L93-L96
Vulnerability details
Impact
The module is expected to have no state changes in case a swap failed, and continue to the conversion phase. It was implemented by swallowing the error with a log and continuing with the flow (erc20 conversion, etc). This is the relevant code section:
In reality, the
TradeInputForExactOutput
function might change the store state and the caller will not revert it in case of an error.For example, the internal
swapCoins
function callsSendCoins
twice. In case the first one succeeded and the second failed and returned an error, the users will lose thein
funds (which will be sent to the pool) and will not receive theout
funds, leading to direct fund loss.When swallowing errors this way, the state should be reverted to what it was right before the operation started, using a cached context.
Proof of Concept
Apply this patch. It will cause swap to error after some state changes happened (one side of the swap):
@@ -19,11 +20,7 @@ func (k Keeper) swapCoins(ctx sdk.Context, sender, recipient sdk.AccAddress, coi return err }
x/onboarding/keeper/ibc_callbacks_test.go
:It will pass, and we can see that the token conversion failed as well (because the math of the remaining tokens to transfer from the escrow address was too high). It means that (artificial!) swap failure can potentially cause loss of funds and disable the conversion.
Tools Used
IDE.
Recommended Mitigation Steps
A cached context should be used, and the store changes should be committed only in case the
TradeInputForExactOutput
call returned no error. One good example is Osmosis's cached context wrapper that can be found here https://github.com/osmosis-labs/osmosis/blob/main/osmoutils/cache_ctx.go .Assessed type
Other