Closed code423n4 closed 1 year ago
JeffCX marked the issue as primary issue
The warden is correct in that a swap could go only half-completed if the function is used incorrectly, but all of the checks should occur before it ever gets to the swap function to ensure it succeeds fully. In our case, all of the checks are done in TradeInputForExactOutput. If the warden believes we are missing any checks which may cause the swap to only half-complete, then that would be great.
I will mark this as valid because, as the warden mentioned, if any future upgrades use the swap function, there may be a potential that they do not do the necessary checks.
tkkwon1998 marked the issue as sponsor confirmed
0xean marked the issue as satisfactory
0xean marked issue #71 as primary and marked this issue as a duplicate of 71
Lines of code
https://github.com/code-423n4/2023-06-canto/blob/a4ff2fd2e67e77e36528fad99f9d88149a5e8532/Canto/x/coinswap/keeper/swap.go#L26 https://github.com/code-423n4/2023-06-canto/blob/a4ff2fd2e67e77e36528fad99f9d88149a5e8532/Canto/x/onboarding/keeper/ibc_callbacks.go#L93-L96 https://github.com/code-423n4/2023-06-canto/blob/a4ff2fd2e67e77e36528fad99f9d88149a5e8532/Canto/x/coinswap/keeper/swap.go#L111-L113 https://github.com/code-423n4/2023-06-canto/blob/a4ff2fd2e67e77e36528fad99f9d88149a5e8532/Canto/x/coinswap/keeper/swap.go#L203-L205
Vulnerability details
Impact
If the last statement of the swapCoins() function returns an error, the swap is only half completed, i.e. only the user's assets are deducted (transferred to the pool), but the user's bought assets are not sent to the user, resulting in a loss of the user's assets.
Proof of Concept
The swapCoins() is implemented as follows:
If the last statement fails, the function returns an error, but this is already the second step of the swap operation, and the first step of deducting the user's assets (transferred to the pool) has already been successfully executed.
Then, let's look at the code in TradeInputForExactOutput() where the
swapCoins()
is called:From the above code we can see that if
k.swapCoins()
returns an error, it is treated as if the swap was not executed, an amount of 0 (sdk.ZeroInt()
) and the error will be returned .The implementation of
TradeExactInputForOutput()
is similar toTradeInputForExactOutput()
.Finally, let's look at how the onboarding's ibc_callbacks.go#OnRecvPacket() handles the call to
TradeInputForExactOutput()
:We can found that if
coinswapKeeper.TradeInputForExactOutput()
returns(0, err)
, the onboarding will treat as if the trade has not been executed entirely(no assets transferring), only an error log is recorded.However, in fact, when the last statement of the
swapCoins()
returns an error (as metioned above), the swap is half executed, some assets transfer has already taken place. The end result is that some of the user's assets are transferred to the coinswap pool, but the user does not receive any Canto.In summary, although the likelihood of the last statement of coinswap's swapCoins() returning an error is very small, and may only occur in an extreme case or in a future update, since it may return an error, it should be handled carefully and logical correctly. And the current implementation logic does not strive to do so.
Tools Used
VS Code
Recommended Mitigation Steps
We should throw this speciall error up in a special way if the swap fails in mid-execution(some of the transfers have already been made), and the upper layer (e.g. onboarding middleware) must identify this specific error and trigger the rollback/recovery of the transaction (maybe in Recovery Middleware) to ensure the safety of the user's assets.
Assessed type
Other