-
Notifications
You must be signed in to change notification settings - Fork 193
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(evm): improve safety of ERC20 transfers, accounting for boolean success return values and recipient balance changes that don't match the ERC20 transfer amount. #2090
Changes from all commits
9d59336
a0afa5a
476ffbc
eb72b31
f356315
43ae3e1
80d5353
3ad846b
e4b932d
b2c863d
2235d9d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,7 +34,8 @@ func (s *Suite) TestERC20Calls() { | |
|
||
s.T().Log("Transfer - Not enough funds") | ||
{ | ||
_, err := deps.EvmKeeper.ERC20().Transfer(contract, deps.Sender.EthAddr, evm.EVM_MODULE_ADDRESS, big.NewInt(9_420), deps.Ctx) | ||
amt := big.NewInt(9_420) | ||
_, err := deps.EvmKeeper.ERC20().Transfer(contract, deps.Sender.EthAddr, evm.EVM_MODULE_ADDRESS, amt, deps.Ctx) | ||
s.ErrorContains(err, "ERC20: transfer amount exceeds balance") | ||
// balances unchanged | ||
evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, deps.Sender.EthAddr, big.NewInt(0)) | ||
|
@@ -43,10 +44,16 @@ func (s *Suite) TestERC20Calls() { | |
|
||
s.T().Log("Transfer - Success (sanity check)") | ||
{ | ||
_, err := deps.EvmKeeper.ERC20().Transfer(contract, evm.EVM_MODULE_ADDRESS, deps.Sender.EthAddr, big.NewInt(9_420), deps.Ctx) | ||
amt := big.NewInt(9_420) | ||
sentAmt, err := deps.EvmKeeper.ERC20().Transfer( | ||
contract, evm.EVM_MODULE_ADDRESS, deps.Sender.EthAddr, amt, deps.Ctx, | ||
) | ||
s.Require().NoError(err) | ||
evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, deps.Sender.EthAddr, big.NewInt(9_420)) | ||
evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, evm.EVM_MODULE_ADDRESS, big.NewInt(60_000)) | ||
evmtest.AssertERC20BalanceEqual( | ||
s.T(), deps, contract, deps.Sender.EthAddr, big.NewInt(9_420)) | ||
evmtest.AssertERC20BalanceEqual( | ||
s.T(), deps, contract, evm.EVM_MODULE_ADDRESS, big.NewInt(60_000)) | ||
s.Require().Equal(sentAmt.String(), amt.String()) | ||
Comment on lines
+47
to
+56
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Enhance test coverage for non-standard ERC20 tokens While this test verifies basic transfer functionality, it doesn't cover the specific cases mentioned in issue #2080 and the PR objectives.
s.T().Log("Transfer - Handle false return value")
{
amt := big.NewInt(1000)
sentAmt, err := deps.EvmKeeper.ERC20().Transfer(
falseReturningToken, // Mock token that returns false
evm.EVM_MODULE_ADDRESS,
deps.Sender.EthAddr,
amt,
deps.Ctx,
)
s.Require().Error(err) // Should handle false return as error
s.Require().Nil(sentAmt)
}
s.T().Log("Transfer - Handle missing return value")
{
amt := big.NewInt(1000)
sentAmt, err := deps.EvmKeeper.ERC20().Transfer(
noReturnToken, // Mock token without return value
evm.EVM_MODULE_ADDRESS,
deps.Sender.EthAddr,
amt,
deps.Ctx,
)
s.Require().NoError(err)
s.Require().Equal(sentAmt.String(), amt.String())
}
-evmtest.AssertERC20BalanceEqual(
- s.T(), deps, contract, deps.Sender.EthAddr, big.NewInt(9_420))
-evmtest.AssertERC20BalanceEqual(
- s.T(), deps, contract, evm.EVM_MODULE_ADDRESS, big.NewInt(60_000))
+// Initial balance (0) + transferred amount (9_420) = 9_420
+evmtest.AssertERC20BalanceEqual(
+ s.T(), deps, contract, deps.Sender.EthAddr, amt)
+// Initial balance (69_420) - transferred amount (9_420) = 60_000
+evmtest.AssertERC20BalanceEqual(
+ s.T(), deps, contract, evm.EVM_MODULE_ADDRESS,
+ new(big.Int).Sub(big.NewInt(69_420), amt)) |
||
} | ||
|
||
s.T().Log("Burn tokens - Allowed as non-owner") | ||
|
Unique-Divine marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -491,30 +491,35 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fungibleTokenMapping := funTokens[0] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if fungibleTokenMapping.IsMadeFromCoin { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return k.convertCoinNativeCoin(ctx, sender, msg.ToEthAddr.Address, msg.BankCoin, fungibleTokenMapping) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return k.convertCoinToEvmBornCoin( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ctx, sender, msg.ToEthAddr.Address, msg.BankCoin, fungibleTokenMapping, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return k.convertCoinNativeERC20(ctx, sender, msg.ToEthAddr.Address, msg.BankCoin, fungibleTokenMapping) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return k.convertCoinToEvmBornERC20( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ctx, sender, msg.ToEthAddr.Address, msg.BankCoin, fungibleTokenMapping, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Converts a native coin to an ERC20 token. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// EVM module owns the ERC-20 contract and can mint the ERC-20 tokens. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
func (k Keeper) convertCoinNativeCoin( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Converts Bank Coins for FunToken mapping that was born from a coin | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// (IsMadeFromCoin=true) into the ERC20 tokens. EVM module owns the ERC-20 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// contract and can mint the ERC-20 tokens. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
func (k Keeper) convertCoinToEvmBornCoin( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ctx sdk.Context, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
sender sdk.AccAddress, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
recipient gethcommon.Address, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
coin sdk.Coin, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
funTokenMapping evm.FunToken, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) (*evm.MsgConvertCoinToEvmResponse, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Step 1: Escrow bank coins with EVM module account | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Step 1: Send Bank Coins to the EVM module | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, evm.ModuleName, sdk.NewCoins(coin)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return nil, errors.Wrap(err, "failed to send coins to module account") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
erc20Addr := funTokenMapping.Erc20Addr.Address | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Step 2: mint ERC-20 tokens for recipient | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Step 2: Mint ERC20 tokens to the recipient | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
evmResp, err := k.CallContract( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ctx, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
embeds.SmartContract_ERC20Minter.ABI, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -542,93 +547,64 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return &evm.MsgConvertCoinToEvmResponse{}, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Converts a coin that was originally an ERC20 token, and that was converted to a bank coin, back to an ERC20 token. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// EVM module does not own the ERC-20 contract and cannot mint the ERC-20 tokens. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// EVM module has escrowed tokens in the first conversion from ERC-20 to bank coin. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
func (k Keeper) convertCoinNativeERC20( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Converts a coin that was originally an ERC20 token, and that was converted to | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// a bank coin, back to an ERC20 token. EVM module does not own the ERC-20 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// contract and cannot mint the ERC-20 tokens. EVM module has escrowed tokens in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// the first conversion from ERC-20 to bank coin. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
func (k Keeper) convertCoinToEvmBornERC20( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ctx sdk.Context, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
sender sdk.AccAddress, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
recipient gethcommon.Address, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
coin sdk.Coin, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
funTokenMapping evm.FunToken, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) (*evm.MsgConvertCoinToEvmResponse, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
erc20Addr := funTokenMapping.Erc20Addr.Address | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
recipientBalanceBefore, err := k.ERC20().BalanceOf(erc20Addr, recipient, ctx) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return nil, errors.Wrap(err, "failed to retrieve balance") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if recipientBalanceBefore == nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return nil, fmt.Errorf("failed to retrieve balance, balance is nil") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Escrow Coins on module account | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// 1 | Caller transfers Bank Coins to be converted to ERC20 tokens. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if err := k.bankKeeper.SendCoinsFromAccountToModule( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ctx, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
sender, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
evm.ModuleName, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
sdk.NewCoins(coin), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return nil, errors.Wrap(err, "failed to escrow coins") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return nil, errors.Wrap(err, "error sending Bank Coins to the EVM") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// verify that the EVM module account has enough escrowed ERC-20 to transfer | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// should never fail, because the coins were minted from the escrowed tokens, but check just in case | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
evmModuleBalance, err := k.ERC20().BalanceOf( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
erc20Addr, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
evm.EVM_MODULE_ADDRESS, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ctx, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return nil, errors.Wrap(err, "failed to retrieve balance") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if evmModuleBalance == nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return nil, fmt.Errorf("failed to retrieve balance, balance is nil") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if evmModuleBalance.Cmp(coin.Amount.BigInt()) < 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return nil, fmt.Errorf("insufficient balance in EVM module account") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// unescrow ERC-20 tokens from EVM module address | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
res, err := k.ERC20().Transfer( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// 2 | EVM sends ERC20 tokens to the "to" account. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// This should never fail due to the EVM account lacking ERc20 fund because | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// the an account must have sent the EVM module ERC20 tokens in the mapping | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// in order to create the coins originally. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Said another way, if an asset is created as an ERC20 and some amount is | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// converted to its Bank Coin representation, a balance of the ERC20 is left | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// inside the EVM module account in order to convert the coins back to | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// ERC20s. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
actualSentAmount, err := k.ERC20().Transfer( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
erc20Addr, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
evm.EVM_MODULE_ADDRESS, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
recipient, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
coin.Amount.BigInt(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ctx, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return nil, errors.Wrap(err, "failed to transfer ERC20 tokens") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if !res { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return nil, fmt.Errorf("failed to transfer ERC20 tokens") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Check expected Receiver balance after transfer execution | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
recipientBalanceAfter, err := k.ERC20().BalanceOf(erc20Addr, recipient, ctx) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return nil, errors.Wrap(err, "failed to retrieve balance") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if recipientBalanceAfter == nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return nil, fmt.Errorf("failed to retrieve balance, balance is nil") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return nil, errors.Wrap(err, "failed to transfer ERC-20 tokens") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
expectedFinalBalance := big.NewInt(0).Add(recipientBalanceBefore, coin.Amount.BigInt()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if r := recipientBalanceAfter.Cmp(expectedFinalBalance); r != 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return nil, fmt.Errorf("expected balance after transfer to be %s, got %s", expectedFinalBalance, recipientBalanceAfter) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Burn escrowed Coins | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
err = k.bankKeeper.BurnCoins(ctx, evm.ModuleName, sdk.NewCoins(coin)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// 3 | In the FunToken ERC20 → BC conversion process that preceded this | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// TxMsg, the Bank Coins were minted. Consequently, to preserve an invariant | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// on the sum of the FunToken's bank and ERC20 supply, we burn the coins here | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// in the BC → ERC20 conversion. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
burnCoin := sdk.NewCoin(coin.Denom, sdk.NewIntFromBigInt(actualSentAmount)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
err = k.bankKeeper.BurnCoins(ctx, evm.ModuleName, sdk.NewCoins(burnCoin)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return nil, errors.Wrap(err, "failed to burn coins") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Emit event with the actual amount received | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
_ = ctx.EventManager().EmitTypedEvent(&evm.EventConvertCoinToEvm{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Sender: sender.String(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Erc20ContractAddress: funTokenMapping.Erc20Addr.String(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ToEthAddr: recipient.String(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
BankCoin: coin, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
BankCoin: burnCoin, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+596
to
+607
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Verify actualSentAmount before burning coins. The code should verify that actualSentAmount is positive before creating and burning coins to handle potential issues with fee-on-transfer tokens. Apply this diff to add the verification: +if actualSentAmount.Sign() <= 0 {
+ return nil, fmt.Errorf("invalid actual sent amount: %s", actualSentAmount)
+}
+
burnCoin := sdk.NewCoin(coin.Denom, sdk.NewIntFromBigInt(actualSentAmount))
err = k.bankKeeper.BurnCoins(ctx, evm.ModuleName, sdk.NewCoins(burnCoin))
if err != nil {
return nil, errors.Wrap(err, "failed to burn coins")
} 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return &evm.MsgConvertCoinToEvmResponse{}, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add test cases for fee-on-transfer tokens
While this test covers the basic insufficient funds scenario, we should add test cases for fee-on-transfer tokens where the received amount differs from the sent amount.
Consider adding these test scenarios: