Skip to content
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

Merged
merged 11 commits into from
Oct 30, 2024

Conversation

matthiasmatt
Copy link
Contributor

@matthiasmatt matthiasmatt commented Oct 22, 2024

Purpose / Abstract

Description:
The current implementation lacks support for certain non-standard ERC20 tokens, specifically those with "Fee on Transfer" mechanisms and those that have missing return values in their transfer functions.

According to the project documentation, these tokens should be properly handled by the system. The issue arises because fee-on-transfer tokens deduct a fee during transfer operations, meaning the recipient receives less than the amount specified. The fee is usually burned or redistributed according to the token's smart contract logic.

The existing code assumes however, that the actual transferred amount is equal to the intended transfer amount, so assertions or checks enforcing this equality fail when interacting with fee-on-transfer tokens, leading to transaction failures or incorrect accounting of token balances.

Similarly, some ERC20 tokens do not return a boolean value upon executing transfer or transferFrom functions, even though the ERC20 standard specifies that these functions should return true on success.

In the x/evm/keeper/erc20.go:73, the Transfer function, the code attempts to unpack the return value of the transfer function. If a token does not return any value, this unpacking operation fails, causing the transaction to revert and making it impossible to interact with such tokens through the current implementation.

There is an existing issue documented in #2063 acknowledging that some tokens do not adhere strictly to the ERC20 standard. While tokens that lack fundamental ERC20 functions (like no transfer function) are rightly considered non-ERC20 and can be excluded, tokens with minor deviations like fee-on-transfer or missing return values are prevalent and widely used.

Recommendation:
We recommend that to improve compatibility and user experience, the system should be updated to handle these "weird" ERC20 token behaviors gracefully. For fee-on-transfer tokens, the transfer logic should be adjusted to not assume that the transfer amount equals the received amount. After performing a transfer, the recipient's balance before and after the transfer should be retrieved to calculate the actual amount received, ensuring that any accounting or state updates reflect the actual amount received.

For tokens with missing return values, function calls should be modified to use low-level calls that do not expect a return value, avoiding unpacking the return value when calling the transfer function. After the transfer call, it's important to verify that the transaction did not revert by checking for errors or ensuring the transaction receipt indicates success.

Additionally, documentation and user guidance should be updated to inform users

Summary by CodeRabbit

  • New Features

    • Enhanced ERC20 token transfer logic now provides the actual amount received after transfers.
    • Introduced debug_traceCall method for improved call tracing.
    • Added validation for FunToken creation fees.
    • Improved handling of gas consumption within ERC20 contract execution.
    • Updated bankSend function to return the actual amount sent during transfers.
  • Bug Fixes

    • Fixed ERC20 metadata utilization during FunToken creation.
    • Corrected gas price representation in RefundGas.
    • Addressed state consistency issues in precompile execution.
    • Improved error handling and validation in the bankSend method.
  • Documentation

    • Updated the CHANGELOG.md to reflect new features and fixes.
  • Tests

    • Enhanced test cases for ERC20 transfer logic and contract response validation.

@matthiasmatt matthiasmatt requested a review from a team as a code owner October 22, 2024 15:17
Copy link
Contributor

coderabbitai bot commented Oct 22, 2024

Caution

Review failed

The pull request is closed.

Walkthrough

The pull request introduces several updates to the Nibiru project, primarily focusing on enhancing ERC20 contract interactions and improving the handling of token transfers. Key changes include modifications to the CHANGELOG.md, updates to method signatures across various files, and the introduction of new methods for better error handling and state management. Notably, the handling of ERC20 transfers has been refined to check for success values and ensure accurate reporting of token amounts transferred.

Changes

File Path Change Summary
CHANGELOG.md Updated to reflect new entries under "Unreleased," detailing fixes, features, and improvements related to ERC20 metadata, gas handling, and FunToken creation.
x/evm/keeper/msg_server.go Modified ConvertCoinToEvm method to utilize new methods for coin conversion; improved logic and event emissions.
x/evm/keeper/erc20.go Updated Transfer method to return balanceIncrease and enhanced error handling for transfer operations.
x/evm/keeper/erc20_test.go Adjusted TestERC20Calls to include validation of transfer amounts and improved clarity in assertions.
x/evm/precompile/funtoken.go Enhanced bankSend method with additional error handling and validation checks for transfer amounts; updated logic for minting and burning tokens.
x/evm/embeds/artifacts/contracts/IFunToken.sol Updated ABI for bankSend function to include an output parameter sentAmount.
x/evm/embeds/artifacts/contracts/TestERC20TransferThenPrecompileSend.sol/TestERC20TransferThenPrecompileSend.json Updated bytecode fields in the JSON artifact.
x/evm/embeds/artifacts/contracts/TestNativeSendThenPrecompileSend.sol/TestNativeSendThenPrecompileSend.json Updated bytecode fields in the JSON artifact.
x/evm/embeds/contracts/IFunToken.sol Modified bankSend function signature to include a return value for sentAmount.
x/evm/precompile/funtoken_test.go Updated tests for funtoken to improve clarity and validate the response from the contract call.

Assessment against linked issues

Objective Addressed Explanation
Handle the case of ERC20.transfer returning success=false no error on the call (#[2080])

Possibly related PRs

🐇 In the garden, I hop with glee,
New tokens flow, as smooth as can be.
With checks in place, and balances right,
Our FunToken shines, a true delight!
Hooray for changes, let’s dance and play,
In the world of ERC20, we leap all day! 🌼


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@matthiasmatt matthiasmatt changed the title feat: fix transfer for native to erc20 fix(evm): transfer for native to erc20 Oct 22, 2024
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 3199ae5 and eb72b31.

📒 Files selected for processing (2)
  • CHANGELOG.md (1 hunks)
  • x/evm/keeper/msg_server.go (2 hunks)
🧰 Additional context used
🔇 Additional comments (1)
CHANGELOG.md (1)

138-138: LGTM! The changelog entry is well-formatted and accurately describes the fix.

The entry properly documents the fix for native to ERC20 token transfers, follows the changelog format guidelines, and aligns with the PR objectives of addressing non-standard ERC20 token handling.

Comment on lines 595 to 598
return nil, errors.Wrap(err, "failed to retrieve EVM module account balance")
}
if recipientBalanceBefore == nil {
return nil, fmt.Errorf("failed to retrieve balance, balance is nil")
return nil, fmt.Errorf("failed to retrieve EVM module account balance, balance is nil")
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Correct error messages to reflect the recipient's balance

The error messages at lines 595 and 598 incorrectly refer to the "EVM module account balance" when they should reference the "recipient's balance" to accurately reflect the operation being performed.

Apply this diff to correct the error messages:

 if err != nil {
-    return nil, errors.Wrap(err, "failed to retrieve EVM module account balance")
+    return nil, errors.Wrap(err, "failed to retrieve recipient balance before transfer")
 }
 if recipientBalanceBefore == nil {
-    return nil, fmt.Errorf("failed to retrieve EVM module account balance, balance is nil")
+    return nil, fmt.Errorf("failed to retrieve recipient balance before transfer, balance is nil")
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return nil, errors.Wrap(err, "failed to retrieve EVM module account balance")
}
if recipientBalanceBefore == nil {
return nil, fmt.Errorf("failed to retrieve balance, balance is nil")
return nil, fmt.Errorf("failed to retrieve EVM module account balance, balance is nil")
return nil, errors.Wrap(err, "failed to retrieve recipient balance before transfer")
}
if recipientBalanceBefore == nil {
return nil, fmt.Errorf("failed to retrieve recipient balance before transfer, balance is nil")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with Code Rabbit - it's not a module account balance.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@onikonychev, glad we agree! Correcting the error messages to reflect the recipient's balance will improve clarity.

(^‿^)/

x/evm/keeper/msg_server.go Outdated Show resolved Hide resolved
Comment on lines 649 to 651
actualReceivedAmount := big.NewInt(0).Sub(recipientBalanceAfter, recipientBalanceBefore)
if actualReceivedAmount.Sign() == 0 {
return nil, fmt.Errorf("no ERC20 tokens were received by the recipient")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure actualReceivedAmount is positive to handle negative values

The current check only verifies if actualReceivedAmount is zero. If the value is negative, the code may proceed incorrectly. Update the condition to check if actualReceivedAmount is less than or equal to zero to handle both zero and negative values.

Apply this diff to update the condition:

 if actualReceivedAmount.Sign() == 0 {
+    return nil, fmt.Errorf("no ERC20 tokens were received by the recipient")
+}
+if actualReceivedAmount.Sign() < 0 {
     return nil, fmt.Errorf("received amount is negative, possible balance inconsistency")
 }

Committable suggestion was skipped due to low confidence.

Comment on lines 595 to 598
return nil, errors.Wrap(err, "failed to retrieve EVM module account balance")
}
if recipientBalanceBefore == nil {
return nil, fmt.Errorf("failed to retrieve balance, balance is nil")
return nil, fmt.Errorf("failed to retrieve EVM module account balance, balance is nil")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with Code Rabbit - it's not a module account balance.

x/evm/keeper/msg_server.go Outdated Show resolved Hide resolved
@matthiasmatt matthiasmatt enabled auto-merge (squash) October 23, 2024 11:04
Copy link

codecov bot commented Oct 23, 2024

Codecov Report

Attention: Patch coverage is 61.22449% with 19 lines in your changes missing coverage. Please review.

Project coverage is 64.52%. Comparing base (16393ca) to head (2235d9d).
Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
x/evm/keeper/erc20.go 41.37% 13 Missing and 4 partials ⚠️
x/evm/keeper/msg_server.go 93.33% 1 Missing ⚠️
x/evm/precompile/funtoken.go 80.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2090      +/-   ##
==========================================
+ Coverage   64.36%   64.52%   +0.15%     
==========================================
  Files         270      271       +1     
  Lines       21192    21235      +43     
==========================================
+ Hits        13640    13701      +61     
+ Misses       6603     6584      -19     
- Partials      949      950       +1     
Files with missing lines Coverage Δ
x/evm/keeper/msg_server.go 75.21% <93.33%> (+3.96%) ⬆️
x/evm/precompile/funtoken.go 46.79% <80.00%> (-0.34%) ⬇️
x/evm/keeper/erc20.go 71.63% <41.37%> (-4.97%) ⬇️

... and 1 file with indirect coverage changes

x/evm/keeper/msg_server.go Outdated Show resolved Hide resolved
x/evm/keeper/msg_server.go Show resolved Hide resolved
Comment on lines 595 to 598
return nil, errors.Wrap(err, "failed to retrieve recipient balance")
}
if recipientBalanceBefore == nil {
return nil, fmt.Errorf("failed to retrieve balance, balance is nil")
return nil, fmt.Errorf("failed to retrieve recipient balance, balance is nil")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we only intend for ERC20 transfers to properly send an amount that alters the recipient balance, let's put the balanceOf recipient checks before and after to inside of ERC20().Transfer. That'll make using it more safe.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (5)
x/evm/keeper/erc20_test.go (1)

Line range hint 1-63: Add documentation for the received parameter

Consider adding comments explaining:

  1. The purpose of the new received parameter in transfer operations
  2. How it helps handle non-standard ERC20 implementations
  3. Its role in accurate balance accounting

This documentation would help other developers understand the changes and their relationship to the audit findings.

x/evm/evmmodule/genesis_test.go (1)

Line range hint 102-104: Update balance verification for fee-on-transfer tokens.

The balance verification assumes the exact transfer amounts, which may not hold true for fee-on-transfer tokens where the received amount could be less than the sent amount.

Consider storing the actual received amounts during transfers and using those for verification:

+  // Store actual received amounts during transfers
+  var (
+    actualReceivedA *big.Int
+    actualReceivedB *big.Int
+  )
+  _, err, actualReceivedA = deps.EvmKeeper.ERC20().Transfer(...)
+  _, err, actualReceivedB = deps.EvmKeeper.ERC20().Transfer(...)

   // Verify erc20 balances for users A, B and sender
   balance, err := deps.EvmKeeper.ERC20().BalanceOf(erc20Addr, toUserA, deps.Ctx)
   s.Require().NoError(err)
-  s.Require().Equal(amountToSendA, balance)
+  s.Require().Equal(actualReceivedA, balance)

   balance, err = deps.EvmKeeper.ERC20().BalanceOf(erc20Addr, toUserB, deps.Ctx)
   s.Require().NoError(err)
-  s.Require().Equal(amountToSendB, balance)
+  s.Require().Equal(actualReceivedB, balance)

Also applies to: 107-109

x/evm/keeper/erc20.go (3)

83-91: Enhance error messages for better debugging

While the error handling is correct, consider adding more context to error messages to aid in debugging transfer failures.

-		return false, fmt.Errorf("failed to pack ABI args: %w", err), received
+		return false, fmt.Errorf("failed to pack transfer args for contract %s: %w", contract.Hex(), err), received

109-122: LGTM: Comprehensive handling of non-standard ERC20 implementations

The implementation successfully handles both standard ERC20 tokens that return boolean values and non-standard ones that don't. Consider adding debug logging for better observability.

 	if err == nil {
+		k.Logger(ctx).Debug("ERC20 transfer returned success value", "success", erc20Bool.Value)
 		success = erc20Bool.Value
 		return success, nil, received
 	}

+	k.Logger(ctx).Debug("ERC20 transfer did not return value, falling back to balance check")
 	success = true
 	return

76-122: Excellent architectural solution for non-standard ERC20 tokens

The implementation provides a robust solution for handling non-standard ERC20 tokens by:

  1. Using balance tracking instead of relying on transfer return values
  2. Supporting fee-on-transfer tokens through actual amount verification
  3. Maintaining compatibility with both standard and non-standard implementations
  4. Providing clear error handling and validation

This approach aligns well with real-world ERC20 token implementations and provides better reliability for token transfers.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between f356315 and 43ae3e1.

📒 Files selected for processing (5)
  • x/evm/evmmodule/genesis_test.go (1 hunks)
  • x/evm/keeper/erc20.go (1 hunks)
  • x/evm/keeper/erc20_test.go (1 hunks)
  • x/evm/keeper/msg_server.go (1 hunks)
  • x/evm/precompile/funtoken.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • x/evm/keeper/msg_server.go
🔇 Additional comments (5)
x/evm/keeper/erc20_test.go (2)

34-39: LGTM! Good test coverage for failed transfers

The addition of the received amount verification enhances the test by ensuring that failed transfers result in zero tokens received, which aligns well with the PR's objective of accurate balance tracking.


44-48: 🛠️ Refactor suggestion

Add test cases for non-standard ERC20 tokens

While the current test verifies basic transfer functionality, consider adding test cases for:

  1. Fee-on-transfer tokens where received amount is less than transfer amount
  2. ERC20 tokens that don't return values from transfer functions

This would ensure complete coverage of the scenarios mentioned in the PR objectives.

Here's a suggested test structure:

s.T().Log("Transfer - Fee on transfer token")
{
    // Mock a fee-on-transfer token where 10% is deducted
    transferAmount := big.NewInt(1000)
    expectedReceived := big.NewInt(900)  // 10% fee
    _, err, received := deps.EvmKeeper.ERC20().Transfer(
        feeToken.Address, 
        evm.EVM_MODULE_ADDRESS, 
        deps.Sender.EthAddr, 
        transferAmount, 
        deps.Ctx,
    )
    s.Require().NoError(err)
    s.Require().Equal(received, expectedReceived)
}

Let's verify if there are any existing test cases for these scenarios:

x/evm/keeper/erc20.go (3)

76-78: LGTM: Method signature enhancement for fee-on-transfer support

The addition of received *big.Int to the return values enables tracking of actual received amounts, which is crucial for handling fee-on-transfer tokens.


79-82: LGTM: Pre-transfer balance tracking implementation

The implementation correctly captures the recipient's initial balance, which is necessary for calculating the actual received amount after transfer.


93-104: LGTM: Robust implementation for fee-on-transfer tokens

The implementation correctly:

  1. Tracks post-transfer balance
  2. Calculates actual received amount
  3. Validates that tokens were actually received
  4. Handles fee-on-transfer tokens by not assuming received = amount

The comments effectively explain the reasoning behind not checking for exact amounts.

@@ -54,11 +54,11 @@ func (s *Suite) TestExportInitGenesis() {
s.Require().NoError(err)

// Transfer ERC-20 tokens to user A
_, err = deps.EvmKeeper.ERC20().Transfer(erc20Addr, fromUser, toUserA, amountToSendA, deps.Ctx)
_, err, _ = deps.EvmKeeper.ERC20().Transfer(erc20Addr, fromUser, toUserA, amountToSendA, deps.Ctx)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Update transfer result handling for fee-on-transfer tokens.

The test ignores the actual received amount (third return value) from Transfer(). For fee-on-transfer tokens, the received amount could be less than the sent amount, which this test wouldn't catch.

Consider updating the test to handle fee-on-transfer scenarios:

-  _, err, _ = deps.EvmKeeper.ERC20().Transfer(erc20Addr, fromUser, toUserA, amountToSendA, deps.Ctx)
+  _, err, receivedAmount := deps.EvmKeeper.ERC20().Transfer(erc20Addr, fromUser, toUserA, amountToSendA, deps.Ctx)
   s.Require().NoError(err)
+  // For fee-on-transfer tokens, verify actual received amount
+  balance, err := deps.EvmKeeper.ERC20().BalanceOf(erc20Addr, toUserA, deps.Ctx)
+  s.Require().NoError(err)
+  s.Require().Equal(receivedAmount, balance)

Also applies to: 61-61

x/evm/evmmodule/genesis_test.go Outdated Show resolved Hide resolved
@@ -159,7 +159,7 @@ func (p precompileFunToken) bankSend(

// Caller transfers ERC20 to the EVM account
transferTo := evm.EVM_MODULE_ADDRESS
_, err = p.evmKeeper.ERC20().Transfer(erc20, caller, transferTo, amount, ctx)
_, err, _ = p.evmKeeper.ERC20().Transfer(erc20, caller, transferTo, amount, ctx)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle the Received Amount from Transfer Method

At line 162:

_, err, _ = p.evmKeeper.ERC20().Transfer(erc20, caller, transferTo, amount, ctx)

The Transfer method now returns an additional value received *big.Int, which represents the actual amount transferred. Ignoring this value may lead to incorrect accounting, especially for tokens with fee-on-transfer mechanisms where the received amount could be less than the requested amount.

Consider capturing and utilizing the received amount to ensure accurate processing:

-receivedAmount, err, _ := p.evmKeeper.ERC20().Transfer(erc20, caller, transferTo, amount, ctx)
+receivedAmount, err, _ := p.evmKeeper.ERC20().Transfer(erc20, caller, transferTo, amount, ctx)
 if err != nil {
     return nil, fmt.Errorf("failed to send from caller to the EVM account: %w", err)
 }
+// Update the amount to reflect the actual received amount
+amount = receivedAmount

By handling receivedAmount, you ensure that subsequent operations use the correct token amount, which is crucial for maintaining consistency and correctness in transactions involving tokens with special transfer behaviors.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (4)
x/evm/keeper/erc20.go (2)

76-78: Update function documentation to reflect new return value.

The method signature has been updated to return the actual received amount, which is great for handling fee-on-transfer tokens. However, the function documentation needs to be updated to explain the new received return value and its significance.

Add this to the function documentation:

 /*
 Transfer implements "ERC20.transfer"
 
     ```solidity
     /// @dev Moves `amount` tokens from the caller's account to `to`.
     /// Returns a boolean value indicating whether the operation succeeded.
     /// Emits a {Transfer} event.
     function transfer(address to, uint256 amount) external returns (bool);
     ```
+
+ @return success - boolean indicating if the transfer was successful
+ @return err - error if any occurred during the transfer
+ @return received - actual amount of tokens received by the recipient, which may be less than
+                   the transfer amount for fee-on-transfer tokens
 */

109-118: Robust handling of non-standard ERC20 implementations but needs test coverage.

The implementation correctly handles both standard and non-standard ERC20 tokens:

  • For standard tokens: Uses the contract's return value
  • For non-standard tokens: Falls back to checking actual token receipt

However, the fallback path (lines 121-122) lacks test coverage. Consider adding test cases for:

  1. Standard ERC20 tokens that return true/false
  2. Non-standard tokens that don't return a value
  3. Tokens that return invalid data

Would you like me to help generate comprehensive test cases for these scenarios?

Also applies to: 121-122

x/evm/keeper/msg_server.go (2)

593-593: Increase test coverage for critical error handling paths

The line added at 593 handles a critical error case but is not covered by tests. Consider adding unit tests to cover this error path to enhance reliability.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 593-593: x/evm/keeper/msg_server.go#L593
Added line #L593 was not covered by tests


578-578: Increase test coverage for nil balance error case

The error handling at line 578 for a nil balance is not covered by tests. Adding unit tests for this scenario will help ensure proper error handling.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 578-578: x/evm/keeper/msg_server.go#L578
Added line #L578 was not covered by tests

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 43ae3e1 and 80d5353.

📒 Files selected for processing (5)
  • CHANGELOG.md (1 hunks)
  • x/evm/keeper/erc20.go (1 hunks)
  • x/evm/keeper/erc20_test.go (1 hunks)
  • x/evm/keeper/msg_server.go (1 hunks)
  • x/evm/precompile/funtoken.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • x/evm/keeper/erc20_test.go
🧰 Additional context used
🪛 GitHub Check: codecov/patch
x/evm/keeper/erc20.go

[warning] 81-82: x/evm/keeper/erc20.go#L81-L82
Added lines #L81 - L82 were not covered by tests


[warning] 86-86: x/evm/keeper/erc20.go#L86
Added line #L86 was not covered by tests


[warning] 95-96: x/evm/keeper/erc20.go#L95-L96
Added lines #L95 - L96 were not covered by tests


[warning] 104-104: x/evm/keeper/erc20.go#L104
Added line #L104 was not covered by tests


[warning] 121-122: x/evm/keeper/erc20.go#L121-L122
Added lines #L121 - L122 were not covered by tests

x/evm/keeper/msg_server.go

[warning] 578-578: x/evm/keeper/msg_server.go#L578
Added line #L578 was not covered by tests


[warning] 593-593: x/evm/keeper/msg_server.go#L593
Added line #L593 was not covered by tests

🔇 Additional comments (3)
x/evm/precompile/funtoken.go (1)

147-147: ⚠️ Potential issue

CRITICAL: Security vulnerability in fee-on-transfer token handling

The current implementation ignores the actual received amount from the Transfer method, which creates a critical vulnerability when handling fee-on-transfer tokens. This could lead to token inflation as the system would mint more bank coins than the actual ERC20 tokens received.

Example scenario:

  1. User transfers 100 tokens of a fee-on-transfer token
  2. Due to the fee mechanism, only 95 tokens are actually received
  3. Current code mints 100 bank coins instead of 95
  4. This creates 5 extra bank coins, breaking the 1:1 peg

Apply this fix to properly handle the received amount and add error handling:

-_, err, _ = p.evmKeeper.ERC20().Transfer(erc20, caller, transferTo, amount, ctx)
+receivedAmount, err, success := p.evmKeeper.ERC20().Transfer(erc20, caller, transferTo, amount, ctx)
 if err != nil {
     return nil, fmt.Errorf("failed to send from caller to the EVM account: %w", err)
 }
+if !success {
+    return nil, fmt.Errorf("transfer failed: ERC20 contract returned false")
+}
+if receivedAmount == nil {
+    return nil, fmt.Errorf("transfer failed: received amount is nil")
+}
+if receivedAmount.Cmp(amount) < 0 {
+    // Update amount to the actual received amount for fee-on-transfer tokens
+    amount = receivedAmount
+}

Run this verification script to identify potential fee-on-transfer tokens in the system:

x/evm/keeper/msg_server.go (2)

578-578: Previous comment still applies: Correct the error message to reflect the recipient's balance

The error message refers to the "EVM module account balance," but it should reference the "recipient's balance" to accurately reflect the operation being performed.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 578-578: x/evm/keeper/msg_server.go#L578
Added line #L578 was not covered by tests


596-597: Previous comment still applies: Ensure actualReceivedAmount is positive

It's important to check that actualReceivedAmount is greater than zero to prevent potential issues with zero or negative amounts during the burn operation.

x/evm/keeper/erc20.go Outdated Show resolved Hide resolved
x/evm/keeper/msg_server.go Outdated Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (3)
x/evm/keeper/erc20.go (3)

79-82: Enhance error context for balance retrieval failures

While the error handling is good, consider adding more context about why the balance check failed, as it could help diagnose issues with non-standard tokens.

-		return false, errors.Wrap(err, "failed to retrieve recipient balance"), received
+		return false, errors.Wrapf(err, "failed to retrieve recipient balance for address %s", to.String()), received

109-122: Add logging for non-standard token behavior

The fallback mechanism for non-standard tokens is good, but consider adding debug logs when encountering such tokens to help with monitoring and debugging.

 	if err == nil {
 		// should be true if the transfer was successful but we do it anyway
 		// to respect the contract's return value
 		success = erc20Bool.Value
 
 		return success, nil, received
 	}
+	// Log non-standard token behavior
+	k.Logger(ctx).Debug(
+		"encountered non-standard ERC20 token",
+		"contract", contract.String(),
+		"error", err.Error(),
+	)
 
 	success = true
 	return

76-122: Add test coverage for fee-on-transfer scenarios

The implementation needs test coverage for:

  1. Fee-on-transfer tokens where received amount < sent amount
  2. Tokens with non-standard return values
  3. Edge cases in balance calculation

Would you like me to help create comprehensive test cases for these scenarios?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 80d5353 and e4b932d.

📒 Files selected for processing (4)
  • CHANGELOG.md (2 hunks)
  • x/evm/keeper/erc20.go (1 hunks)
  • x/evm/keeper/msg_server.go (1 hunks)
  • x/evm/precompile/funtoken.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • CHANGELOG.md
  • x/evm/precompile/funtoken.go
🔇 Additional comments (5)
x/evm/keeper/erc20.go (3)

76-78: LGTM: Method signature enhancement for fee-on-transfer support

The addition of received *big.Int as a return value enables proper tracking of actual received tokens, which is crucial for fee-on-transfer tokens.


86-91: LGTM: Consistent error handling

The error handling maintains a consistent pattern of returning the received amount even in error cases.


93-104: Verify balance calculation safety

While the balance difference calculation is logically correct, consider:

  1. Using SafeSub to prevent integer overflow
  2. The possibility of concurrent transfers affecting the balance

Let's check if there are any existing safety measures:

x/evm/keeper/msg_server.go (2)

592-593: Handle nil error correctly when transfer fails

If err is nil but success is false, using errors.Wrap(err, ...) with a nil error may result in an unintended error message. Update the error handling to correctly construct the error message when err is nil.


578-578: Correct error message to reflect the EVM module account balance

The error message refers to the "EVM module account balance" when retrieving the module's balance. Ensure this accurately reflects the account involved in the operation.

x/evm/keeper/msg_server.go Outdated Show resolved Hide resolved
@Unique-Divine Unique-Divine enabled auto-merge (squash) October 30, 2024 00:00
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Outside diff range and nitpick comments (4)
x/evm/embeds/artifacts/contracts/IFunToken.sol/IFunToken.json (1)

25-31: Excellent addition of sentAmount return value for handling non-standard tokens!

This change is crucial for properly handling "Fee on Transfer" tokens where the actual amount received differs from the amount sent. By returning the sentAmount, the contract can now accurately track token transfers and maintain correct balance accounting.

Consider documenting in the interface comments that:

  1. For standard ERC20 tokens, sentAmount will equal the input amount
  2. For fee-on-transfer tokens, sentAmount may be less than the input amount
  3. This return value should be used for balance tracking instead of the input amount
x/evm/embeds/contracts/IFunToken.sol (1)

8-14: LGTM! Consider adding examples for clarity.

The documentation clearly explains the parameters and the return value, particularly highlighting the potential difference between sent and received amounts for fee-on-transfer tokens.

Consider adding a documentation example to illustrate the fee-on-transfer scenario:

    /// @return sentAmount - amount of tokens received by the recipient. This may
    /// not be equal to `amount` if the corresponding ERC20 contract has a fee or
    /// deduction on transfer.
+   /// @dev Example: If amount=100 and token has 2% transfer fee, sentAmount=98
x/evm/keeper/erc20_test.go (1)

Line range hint 1-56: Consider restructuring tests for better isolation

The current test structure combines multiple scenarios in a single test function. This makes it harder to understand failures and maintain the tests.

Consider these improvements:

  1. Split into separate test functions:
func (s *Suite) TestERC20Transfer_InsufficientFunds() {}
func (s *Suite) TestERC20Transfer_Success() {}
func (s *Suite) TestERC20Transfer_FeeOnTransfer() {}
func (s *Suite) TestERC20Transfer_NoReturnValue() {}
func (s *Suite) TestERC20Transfer_FalseReturn() {}
  1. Add helper functions for token deployment:
func (s *Suite) deployMockToken(opts MockTokenOpts) *evmtest.FunToken {
    // Configure token behavior (fees, return values, etc.)
}

type MockTokenOpts struct {
    HasFeeOnTransfer bool
    FeePercentage   uint8
    ReturnsValue    bool
    AlwaysReturns   bool
}
x/evm/keeper/erc20.go (1)

111-119: Consider improving error message formatting.

The implementation correctly handles fee-on-transfer tokens by tracking actual balance changes. However, the error message could be more readable.

-			"amount of ERC20 tokens received MUST be positive: the balance of recipient %s would've changed by %v for token %s",
-			to.Hex(), balanceIncrease.String(), contract.Hex(),
+			"amount of ERC20 tokens received MUST be positive: recipient=%s received_amount=%v token=%s",
+			to.Hex(), balanceIncrease.String(), contract.Hex(),
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between e4b932d and b2c863d.

📒 Files selected for processing (10)
  • CHANGELOG.md (1 hunks)
  • x/evm/embeds/artifacts/contracts/IFunToken.sol/IFunToken.json (1 hunks)
  • x/evm/embeds/artifacts/contracts/TestERC20TransferThenPrecompileSend.sol/TestERC20TransferThenPrecompileSend.json (1 hunks)
  • x/evm/embeds/artifacts/contracts/TestNativeSendThenPrecompileSend.sol/TestNativeSendThenPrecompileSend.json (1 hunks)
  • x/evm/embeds/contracts/IFunToken.sol (1 hunks)
  • x/evm/keeper/erc20.go (1 hunks)
  • x/evm/keeper/erc20_test.go (2 hunks)
  • x/evm/keeper/msg_server.go (3 hunks)
  • x/evm/precompile/funtoken.go (2 hunks)
  • x/evm/precompile/funtoken_test.go (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • x/evm/embeds/artifacts/contracts/TestERC20TransferThenPrecompileSend.sol/TestERC20TransferThenPrecompileSend.json
  • x/evm/embeds/artifacts/contracts/TestNativeSendThenPrecompileSend.sol/TestNativeSendThenPrecompileSend.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • CHANGELOG.md
  • x/evm/precompile/funtoken.go
🔇 Additional comments (9)
x/evm/embeds/contracts/IFunToken.sol (1)

15-19: LGTM! Verify implementation in precompile.

The addition of sentAmount return value is a crucial enhancement for handling fee-on-transfer tokens correctly. This change aligns perfectly with the PR objectives and maintains backward compatibility.

Let's verify the precompile implementation handles this new return value:

✅ Verification successful

The initial search revealed the precompile exists but we need to verify:

  1. If the implementation returns the actual sent amount
  2. If there are tests covering this functionality

The new search will help us see the complete implementation and any test coverage.


The previous search showed the beginning of the bankSend implementation and some test references, but we need to see:

  1. The complete implementation to verify if it returns the actual sent amount
  2. The test contract implementation to understand how the return value is handled in tests

We need to see:

  1. The rest of the bankSend implementation, particularly how it handles the return value after validating the address
  2. The location of the test contract to verify how it handles the return value

The previous search was cut off at the address validation part, and we couldn't find the test contract in the expected location.


Implementation correctly handles fee-on-transfer tokens and returns actual sent amount

The precompile implementation properly handles fee-on-transfer tokens by:

  1. Capturing the actual transferred amount via gotAmount from ERC20().Transfer()
  2. Using this gotAmount for subsequent operations (minting/burning)
  3. Correctly returning the actual sent amount via method.Outputs.Pack(gotAmount)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the precompile implementation matches the interface changes

# Test 1: Check if the precompile code handles the new return value
rg -A 10 "func.*[Bb]ank[Ss]end.*" 

# Test 2: Look for related test cases
rg -A 10 "TestBankSend.*fee.*transfer" 

Length of output: 2063


Script:

#!/bin/bash
# Let's check the implementation details of bankSend in the precompile
rg -B 5 -A 20 "func \(p precompileFunToken\) bankSend" x/evm/precompile/funtoken.go

# Also check if there are any test files related to bankSend
fd ".*test.*\.go$" | xargs rg "bankSend"

Length of output: 1692


Script:

#!/bin/bash
# Let's see the complete implementation of bankSend and its return value handling
rg -B 20 -A 40 "func \(p precompileFunToken\) bankSend" x/evm/precompile/funtoken.go

# Also check the test contract implementation to see how it handles the return value
rg -A 20 "contract.*TestContract" x/evm/keeper/testdata/contracts/

Length of output: 1963


Script:

#!/bin/bash
# Let's see the complete implementation by searching more lines after the last visible part
rg -A 60 "toAddr, err := sdk.AccAddressFromBech32\(to\)" x/evm/precompile/funtoken.go

# Also try to find the test contract in a different location
fd -t f "TestContract.sol"

Length of output: 2022

x/evm/precompile/funtoken_test.go (3)

129-136: LGTM! Improved variable naming.

The variable rename from resp to ethTxResp enhances code readability by better describing the response type from the EVM transaction.


138-143: LGTM! Improved test readability.

The multi-line formatting of balance assertions enhances readability while maintaining the critical checks for token balance accuracy.


148-156: 🛠️ Refactor suggestion

Add test cases for fee-on-transfer tokens.

While the new validation is good, consider adding test cases that verify the handling of fee-on-transfer tokens where the received amount differs from the sent amount. This would better align with the PR objectives of handling non-standard ERC20 tokens.

Example test case structure:

s.T().Log("Test fee-on-transfer token")
{
    // Mock a fee-on-transfer token where recipient receives 90% of sent amount
    amtToSend := int64(1000)
    expectedReceived := int64(900)  // 90% of sent amount
    
    // ... perform transfer ...
    
    // Verify actual received amount
    var sentAmt *big.Int
    err = embeds.SmartContract_FunToken.ABI.UnpackIntoInterface(
        &sentAmt,
        string(precompile.FunTokenMethod_BankSend),
        ethTxResp.Ret,
    )
    s.NoError(err)
    s.Require().Equal(expectedReceived, sentAmt.Int64())
}

Let me verify if there are any existing test cases for fee-on-transfer tokens:

x/evm/keeper/erc20.go (3)

77-81: LGTM! Balance tracking implementation is correct.

The implementation properly retrieves and tracks the recipient's balance before the transfer, with appropriate error handling and descriptive error messages.


98-102: LGTM! Improved error handling for non-reverting transfers.

The explicit success check addresses issue #2080, properly handling tokens like ZRX and BAT that return false instead of reverting on failed transfers.


76-77: LGTM! Verify callers of this method.

The signature change from (bool, error) to (*big.Int, error) is a good improvement that allows tracking the actual amount of tokens received, which is crucial for fee-on-transfer tokens.

✅ Verification successful

Let me analyze the callers of the Transfer method to ensure we have a complete picture.


Let me check one more pattern to ensure we haven't missed any direct calls to Transfer.


All callers of Transfer method have been updated correctly

Based on the verification results:

  • The only caller that uses the returned balance value is in precompile/funtoken.go, which correctly captures it as gotAmount
  • All other callers in test files only check the error return value using _, err := pattern, which is appropriate since they don't need the balance change
  • No instances of incorrect usage patterns were found
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all callers of the Transfer method to ensure they're updated
# to handle the new return value.

# Search for method calls
rg -B 2 -A 2 "Transfer\s*\([^)]*\)" --type go

Length of output: 8260


Script:

#!/bin/bash
# Let's look at the actual implementation of the Transfer method callers
# to see how they handle the return value

# First, let's check the precompile/funtoken.go implementation
ast-grep --pattern 'gotAmount, err := p.evmKeeper.ERC20().Transfer($$$)'

# Then check the test implementations
ast-grep --pattern '_, err := $$.Transfer($$$)'
ast-grep --pattern 'err := $$.Transfer($$$)'

Length of output: 292


Script:

#!/bin/bash
# Search for any Transfer method calls that might store the return value
# in a variable or use it directly
ast-grep --pattern '$$ := $$.Transfer($$$)'
ast-grep --pattern '$$ = $$.Transfer($$$)'

# Also check for direct usage in if statements or function calls
ast-grep --pattern 'if $$.Transfer($$$)'
ast-grep --pattern '$$.Transfer($$$)'

Length of output: 173

x/evm/keeper/msg_server.go (2)

494-496: LGTM! Clear separation of concerns based on token origin.

The code now correctly routes the conversion based on whether the token originated from a coin or ERC20, improving the handling of different token types.


Line range hint 500-544: LGTM! Well-structured implementation with proper error handling.

The function follows a clear two-step process with proper error handling and event emission. The implementation correctly handles the minting of ERC20 tokens for coin-originated tokens.

Comment on lines +37 to +38
amt := big.NewInt(9_420)
_, err := deps.EvmKeeper.ERC20().Transfer(contract, deps.Sender.EthAddr, evm.EVM_MODULE_ADDRESS, amt, deps.Ctx)
Copy link
Contributor

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:

s.T().Log("Transfer - Fee on transfer token")
{
    amt := big.NewInt(1000)
    expectedReceivedAmt := big.NewInt(950) // Assuming 5% fee
    sentAmt, err := deps.EvmKeeper.ERC20().Transfer(
        feeToken, // Mock fee-on-transfer token
        evm.EVM_MODULE_ADDRESS,
        deps.Sender.EthAddr,
        amt,
        deps.Ctx,
    )
    s.Require().NoError(err)
    s.Require().Equal(sentAmt.String(), expectedReceivedAmt.String())
}

Comment on lines +47 to +56
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())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

  1. Add test cases for tokens that return false instead of reverting:
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)
}
  1. Add test cases for tokens without return values:
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())
}
  1. Make balance assertions more explicit:
-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))

Comment on lines +598 to +609
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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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,
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")
}
// Emit event with the actual amount received
_ = ctx.EventManager().EmitTypedEvent(&evm.EventConvertCoinToEvm{
Sender: sender.String(),
Erc20ContractAddress: funTokenMapping.Erc20Addr.String(),
ToEthAddr: recipient.String(),
BankCoin: burnCoin,

x/evm/keeper/msg_server.go Outdated Show resolved Hide resolved
@Unique-Divine Unique-Divine changed the title fix(evm): transfer for native to erc20 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. Oct 30, 2024
@Unique-Divine Unique-Divine merged commit 9d0dbd5 into main Oct 30, 2024
14 checks passed
@Unique-Divine Unique-Divine deleted the mat/audit-32 branch October 30, 2024 11:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[evm] bug: Handle the case of ERC20.transfer returning success=false no error on the call
3 participants