Organization
- @coinbase
Engagement Type
Cantina Reviews
Period
-
Repositories
Researchers
Findings
Low Risk
2 findings
2 fixed
0 acknowledged
Informational
8 findings
6 fixed
2 acknowledged
Low Risk2 findings
Default address fee recipient
State
Severity
- Severity: Low
Submitted by
Gerard Persoon
Description
The original
initialize()allowed the default address for the fee recipient.The new
initialize()checks for this viarequire_recipient_set().Recommendation
Consider checking this in
do_migrate_authorities().Incorrect value used in test
State
Severity
- Severity: Low
Submitted by
Gerard Persoon
Description
A test for
UnpauseusespauseAuthority, which looks to be incorrect.Recommendation
Consider changing the test to:
.accounts({ pool,- pauseAuthority: pauseAuthority.publicKey,+ unpauseAuthority: unpauseAuthority.publicKey, })- .signers([pauseAuthority.payer])+ .signers([unpauseAuthority.payer])
Informational8 findings
User Funded ATA Creation for Fee Recipient
State
- Acknowledged
Severity
- Severity: Informational
Submitted by
Jay
Description
When
update_fee_recipientis called, only the pubkey is updated in the pool state. No associated token accounts are created for the new fee recipient. During subsequent swaps, thefee_recipient_token_accountusesinit_if_neededwith the swapping user as payer. This means the first user to swap each supported token after a fee recipient change will pay approximately 0.002 SOL to create the new recipients ATA.In contrast, when adding a new token via
add_supported_token, theconfigure_authoritypays for the fee recipients ATA creation.Recommendation
Consider requiring the
configure_authorityto pre create all necessary ATAs for the new fee recipient within theupdate_fee_recipientinstruction. This would align the cost model withadd_supported_tokenand shift the burden to the party making the configuration change.Coinbase
Good point, but this is out of scope for this review.
Missing non zero min_amount_out check in Solana swap compared to EVM
State
Severity
- Severity: Informational
Submitted by
Jay
Description
The Solana swap instruction validates that
amount_inis greater than zero and that the computedamount_outis greater than zero, but it never requiresmin_amount_outto be greater than zero. Because the only slippage guard isamount_out>=min_amount_out, a caller who passesmin_amount_outequal to zero reduces that guard to a comparison that always succeeds, so slippage protection is effectively turned off for that swap.The EVM implementation does not permit this. Its swap function explicitly rejects a zero minimum, forcing every caller to commit to a real minimum output before the swap executes. The relevant EVM guard is on L344 in function
swap().This is a behavioral divergence between the two chains for the same product. The two implementations are meant to mirror each other, yet the Solana side accepts an input that the EVM side treats as invalid. This is a small parity and hardening note to further tighten the codebase.
Recommendation
Add an explicit check that
min_amount_outis greater than zero at the start of the swap instruction, mirroring the EVMCannotBeZeroAmountguard so both chains enforce the same invariant:require!(amount_in > 0, LiquidityError::InvalidAmount);+ require!(min_amount_out > 0, LiquidityError::InvalidAmount);This forces every caller to specify a real minimum output and removes the ability to submit a swap with slippage protection disabled, bringing the Solana behavior back in line with the EVM contract.
Hot key comment not accurate
State
Severity
- Severity: Informational
Submitted by
Gerard Persoon
Description
The hot key comment is not accurate because there is a list of allowed
withdraw_recipients.Recommendation
Consider changing the comment in the following way:
- /// Hot key allowed to withdraw liquidity (only to `withdraw_recipient`).+ /// Hot key allowed to withdraw liquidity (only to one of the `withdraw_recipients`).MIGRATION_GROWTH is only used in comments
State
Severity
- Severity: Informational
Submitted by
Gerard Persoon
Description
The variable
MIGRATION_GROWTHis only used in comments.Recommendation
Consider removing the variable or turning the line into a comment.
.key() versus Pubkey
State
- Acknowledged
Severity
- Severity: Informational
Submitted by
Gerard Persoon
Description
Function
initialize()uses.key()whilemigrate_authorities()and most other functions usesPubkey. The result is the same but its inconsistent.Recommendation
Consider using the same convention everywhere. However because it changes the interface is might not be worth the trouble.
Initial state of a newly added token
State
Severity
- Severity: Informational
Submitted by
Gerard Persoon
Description
The initial state of a newly added token is different between EVM and Solana. On the EVM the initial state is that
swappable=falseand on Solana the initial state isvault.disabled = false;, which means it is swappable.Both versions are meant to have the same functionality.
Recommendation
Consider using the same approach on both versions.
“Liquidity paused” not clear
State
Severity
- Severity: Informational
Submitted by
Gerard Persoon
Description
“Liquidity paused” means withdrawals paused, however swaps and direct deposits are still allowed. So the naming "LiquidityPaused" and the error message is confusing.
Recommendation
Consider renaming "LiquidityPaused" to "Withdrawel paused", both the error string and the variable name.
Comments in stable-swapper.ts are obsolete
State
Severity
- Severity: Informational
Submitted by
Gerard Persoon
Description
Some comments in
stable-swapper.tsare obsolete and confusing.Recommendation
Consider changing the comments in the following way, or remove the comments.
- .pauseWithdraws() // swapsPaused=null, liquidityPaused=true+ .pauseWithdraws() // Sets liquidityPaused=true; swapsPaused is unchanged- .pauseSwaps() // swapsPaused, liquidityPaused+ .pauseSwaps() // Sets swapsPaused=true; liquidityPaused is unchanged- .unpauseSwaps() // swapsPaused, liquidityPaused+ .unpauseSwaps() // Sets swapsPaused=false; liquidityPaused is unchanged