Skip to content

Conversation

ChiTimesChi
Copy link
Collaborator

@ChiTimesChi ChiTimesChi commented Jun 5, 2025

Summary by CodeRabbit

  • New Features

    • Introduced a contract for migrating tokens from an old version to a new one, supporting conversion with different decimal places.
    • Users can preview migration outcomes and perform token swaps directly.
    • Owners can register new token pairs for migration.
  • Bug Fixes

    • Enhanced error handling for invalid addresses, duplicate pairs, and zero-value transactions.
  • Tests

    • Added comprehensive tests for migration logic, management functions, and scenarios with varying token decimals.
    • Introduced mock tokens for robust testing of migration and burning functionalities.

Copy link

coderabbitai bot commented Jun 5, 2025

Walkthrough

A new token migration system is introduced, including the SynapseMigrator contract, supporting interfaces, custom errors, and extensive test suites. The migrator enables secure migration from old tokens to new tokens, handling decimal conversions, ownership controls, and error scenarios. Supporting mock contracts and tests cover various migration precision and management cases.

Changes

File(s) Change Summary
contracts/migrator/SynapseMigrator.sol Added SynapseMigrator contract for token migration, with owner controls, event emission, and decimal handling.
contracts/migrator/interfaces/IBurnable.sol Added IBurnable interface declaring burnFrom(address,uint256).
contracts/migrator/interfaces/ISynapseMigrator.sol Added ISynapseMigrator interface with migration, management, and preview functions.
contracts/migrator/interfaces/ISynapseMigratorErrors.sol Added ISynapseMigratorErrors interface with custom error declarations.
test/mocks/MockBurnableToken.sol Added MockBurnableToken for tests, supporting minting, custom decimals, and burning.
test/migrator/SynapseMigrator.t.sol Added base test suite SynapseMigratorTest for migration logic and edge cases.
test/migrator/SynapseMigrator.LessDecimals.t.sol Added tests for migration with fewer decimals, focusing on precision loss.
test/migrator/SynapseMigrator.MoreDecimals.t.sol Added tests for migration with more decimals, via subclassing base test.
test/migrator/SynapseMigrator.SameDecimals.t.sol Added tests for migration with same decimals, via subclassing base test.
test/migrator/SynapseMigrator.Management.t.sol Added tests for migrator management, ownership, and token pair registration.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant SynapseMigrator
    participant OldToken (IBurnable)
    participant NewToken (ERC20)

    User->>SynapseMigrator: migrate(oldToken, amount)
    SynapseMigrator->>OldToken: burnFrom(User, amount)
    SynapseMigrator->>NewToken: transfer(User, scaledAmount)
    SynapseMigrator-->>User: emit Migrated event
Loading

Poem

In fields of code where tokens roam,
A migrator builds a brand new home.
Old coins burned, new ones appear,
Decimal dreams now crystal clear!
Rabbits hop and tests abound—
Migration magic, safe and sound.
🐇✨


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ 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.
    • Explain this complex logic.
    • 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 explain this code block.
    • @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 explain its main purpose.
    • @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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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 generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this 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.

ChiTimesChi and others added 3 commits June 9, 2025 18:42
Update ISynapseMigrator interface and implementation to return both newToken
and newAmount from previewMigrate, eliminating the need for internal
_previewMigrate function and simplifying the contract logic.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
@coveralls
Copy link

Pull Request Test Coverage Report for Build 15740219775

Details

  • 0 of 19 (0.0%) changed or added relevant lines in 1 file are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage decreased (-0.06%) to 12.788%

Changes Missing Coverage Covered Lines Changed/Added Lines %
contracts/migrator/SynapseMigrator.sol 0 19 0.0%
Totals Coverage Status
Change from base Build 15739971121: -0.06%
Covered Lines: 664
Relevant Lines: 4733

💛 - Coveralls

@ChiTimesChi ChiTimesChi marked this pull request as ready for review June 27, 2025 11:05
Copy link

@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

🧹 Nitpick comments (6)
contracts/migrator/interfaces/IBurnable.sol (1)

4-6: Interface looks clean but consider ERC20 compatibility.

The IBurnable interface is minimal and focused, which is good. However, consider if this should extend or be compatible with OpenZeppelin's ERC20Burnable interface for broader compatibility.

Consider adding documentation comments to clarify the interface's purpose:

+/// @title IBurnable
+/// @notice Interface for tokens that support burning from another address
 interface IBurnable {
+    /// @notice Burns a specific amount of tokens from the given address
+    /// @param from The address to burn tokens from
+    /// @param amount The amount of tokens to burn
     function burnFrom(address from, uint256 amount) external;
 }
contracts/migrator/interfaces/ISynapseMigratorErrors.sol (1)

4-10: Well-structured error definitions with consistent naming.

The custom errors are well-defined with a clear naming convention using the SM__ prefix. The error names are descriptive and cover the expected failure scenarios for a token migrator.

Consider adding documentation comments to explain when each error is thrown:

+/// @title ISynapseMigratorErrors
+/// @notice Custom errors used by the SynapseMigrator contract
 interface ISynapseMigratorErrors {
+    /// @notice Thrown when the same address is provided for both old and new tokens
     error SM__SameAddress();
+    /// @notice Thrown when attempting to add a token pair that already exists
     error SM__TokenPairAlreadyAdded();
+    /// @notice Thrown when attempting to migrate with a token pair that doesn't exist
     error SM__TokenPairNotAdded();
+    /// @notice Thrown when a zero address is provided as input
     error SM__ZeroAddress();
+    /// @notice Thrown when a zero amount is provided for migration
     error SM__ZeroAmount();
 }
test/mocks/MockBurnableToken.sol (1)

22-24: Consider adding access control to mintTestTokens.

The mintTestTokens function is public and unrestricted, which is appropriate for testing but could be made more explicit about its testing-only nature.

Consider adding a comment or making the testing nature more explicit:

+    /// @notice Mint tokens for testing purposes only
+    /// @dev This function should only be used in tests
     function mintTestTokens(address to, uint256 amount) external {
         _mint(to, amount);
     }
test/migrator/SynapseMigrator.SameDecimals.t.sol (1)

6-8: Appropriate test for same decimal precision scenario.

The test contract correctly configures both old and new tokens to have 18 decimals, testing the migration scenario where no decimal scaling is required. This is an important edge case to verify.

Consider adding a comment to clarify the test scenario:

+/// @notice Test contract for migrating tokens with the same decimal precision (18 -> 18)
 contract SynapseMigratorSameDecimalsTest is SynapseMigratorTest {
     constructor() SynapseMigratorTest(18, 18) {}
 }
test/migrator/SynapseMigrator.t.sol (2)

11-11: Consider making the abstract contract more explicit.

The abstract contract is not explicitly marked as abstract, which could be confusing. Consider adding the abstract keyword for clarity.

-abstract contract SynapseMigratorTest is Test, ISynapseMigratorErrors {
+abstract contract SynapseMigratorTest is Test, ISynapseMigratorErrors {

27-30: Constructor parameters should be validated.

The constructor accepts decimal parameters but doesn't validate them. Consider adding basic validation to prevent invalid test configurations.

 constructor(uint8 oldTokenDecimals_, uint8 newTokenDecimals_) {
+    require(oldTokenDecimals_ <= 18, "Invalid old token decimals");
+    require(newTokenDecimals_ <= 18, "Invalid new token decimals");
     oldTokenDecimals = oldTokenDecimals_;
     newTokenDecimals = newTokenDecimals_;
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 60f1c25 and 803dd05.

📒 Files selected for processing (10)
  • contracts/migrator/SynapseMigrator.sol (1 hunks)
  • contracts/migrator/interfaces/IBurnable.sol (1 hunks)
  • contracts/migrator/interfaces/ISynapseMigrator.sol (1 hunks)
  • contracts/migrator/interfaces/ISynapseMigratorErrors.sol (1 hunks)
  • test/migrator/SynapseMigrator.LessDecimals.t.sol (1 hunks)
  • test/migrator/SynapseMigrator.Management.t.sol (1 hunks)
  • test/migrator/SynapseMigrator.MoreDecimals.t.sol (1 hunks)
  • test/migrator/SynapseMigrator.SameDecimals.t.sol (1 hunks)
  • test/migrator/SynapseMigrator.t.sol (1 hunks)
  • test/mocks/MockBurnableToken.sol (1 hunks)
🔇 Additional comments (15)
test/mocks/MockBurnableToken.sol (2)

11-13: Constructor implementation is correct.

The constructor properly initializes the ERC20 token with the provided name and stores the custom decimals value.


4-4: Verify OpenZeppelin version compatibility.

The contract imports from a specific OpenZeppelin version @openzeppelin/contracts-4.5.0. Ensure this version is compatible with the Solidity version ^0.8.0 used in this contract.

#!/bin/bash
# Description: Check if OpenZeppelin 4.5.0 is compatible with Solidity ^0.8.0
# and if there are any known security issues with this version

# Check for any references to OpenZeppelin version requirements in the codebase
rg -A 3 -B 3 "openzeppelin.*4\.5\.0"

# Look for any package.json or similar files that might specify OpenZeppelin versions
fd -e json -e toml -e yaml | xargs grep -l "openzeppelin" 2>/dev/null || true
test/migrator/SynapseMigrator.MoreDecimals.t.sol (1)

6-8: ```bash
#!/bin/bash

Preview base test contract and search for decimal logic

FILE="test/migrator/SynapseMigrator.t.sol"

echo "=== First 200 lines of $FILE ==="
sed -n '1,200p' "$FILE" || echo "Failed to read $FILE"

echo -e "\n=== Search for decimal parameters ==="
rg -n "decimals" -g "$FILE" || echo "No 'decimals' occurrences found"
rg -n "decimal" -g "$FILE" || echo "No 'decimal' occurrences found"

echo -e "\n=== Search for scaling operations ==="
rg -n "mul" -g "$FILE" || echo "No 'mul' occurrences found"
rg -n "div" -g "$FILE" || echo "No 'div' occurrences found"


</details>
<details>
<summary>contracts/migrator/interfaces/ISynapseMigrator.sol (1)</summary>

`1-32`: **Well-designed interface with comprehensive documentation.**

The interface is well-structured with clear function signatures and comprehensive documentation. The revert conditions are properly documented, making it easy for implementers to understand the expected behavior.

</details>
<details>
<summary>test/migrator/SynapseMigrator.t.sol (2)</summary>

`52-65`: **Comprehensive migration test with good balance verification.**

The test properly verifies all aspects of the migration:
- Event emission
- Balance changes for both tokens
- Total supply changes (old token burned, new token transferred)
This ensures the migration logic works correctly.

---

`87-93`: **Good test for allowance checking.**

The test properly verifies that insufficient allowance is handled correctly. The use of `vm.expectRevert()` without a specific error selector is acceptable here since the revert comes from the ERC20 token itself.

</details>
<details>
<summary>test/migrator/SynapseMigrator.Management.t.sol (3)</summary>

`24-38`: **Good use of mock calls for external dependencies.**

The setup properly mocks the `decimals()` calls for the token contracts, which isolates the tests from external dependencies and makes them more reliable.

---

`46-49`: **Test should use custom error instead of string message.**

The test expects a string revert message from OpenZeppelin's Ownable, but it would be more consistent to expect a custom error. However, since this is testing OpenZeppelin's behavior, the current approach is acceptable.

---

`91-96`: **Excellent use of fuzzing for access control testing.**

The fuzz test with `vm.assume(caller != owner)` is a great way to ensure that any non-owner address cannot call the restricted function.

</details>
<details>
<summary>contracts/migrator/SynapseMigrator.sol (3)</summary>

`26-28`: **Constructor properly transfers ownership.**

The constructor correctly transfers ownership to the specified address, which is the proper pattern for OpenZeppelin's Ownable.

---

`31-43`: **Comprehensive input validation and proper state management.**

The `addTokenPair` function includes all necessary validations:
- Zero address checks
- Same address prevention
- Duplicate pair prevention
- Proper event emission

The storage of token decimals in the struct is a good optimization to avoid repeated external calls.

---

`52-52`: ```shell
#!/bin/bash
# Display the IBurnable interface definition for verification
sed -n '1,200p' contracts/migrator/interfaces/IBurnable.sol
test/migrator/SynapseMigrator.LessDecimals.t.sol (3)

10-14: Good test for precision loss handling.

The test uses a realistic decimal amount (1.23456789 tokens) and verifies that precision loss results in the expected truncated amount (1234567 units with 6 decimals = 1.234567 tokens).


16-20: Excellent edge case test for zero amount output.

This test verifies that when the conversion would result in zero output due to precision loss, the contract properly reverts with SM__ZeroAmount. The amount 10**12 - 1 is just below the threshold that would produce 1 unit in the new token.


28-32: Preview function correctly handles precision loss edge case.

The test verifies that the preview function returns zero for amounts that would result in zero output, which is consistent with the migration function's behavior.

Comment on lines +64 to +74
function previewMigrate(address oldToken, uint256 amount)
public
view
returns (address newToken, uint256 newAmount)
{
newToken = _tokenPairs[oldToken].newToken;
uint256 oldDecimals = _tokenPairs[oldToken].oldTokenDecimals;
uint256 newDecimals = _tokenPairs[oldToken].newTokenDecimals;
if (newToken == address(0)) return (address(0), 0);
newAmount = (amount * 10**newDecimals) / 10**oldDecimals;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Decimal conversion logic needs overflow protection.

The decimal conversion calculation (amount * 10**newDecimals) / 10**oldDecimals could potentially overflow for large amounts, especially with high decimal differences.

Consider using a safer calculation approach or adding overflow checks:

 function previewMigrate(address oldToken, uint256 amount)
     public
     view
     returns (address newToken, uint256 newAmount)
 {
     newToken = _tokenPairs[oldToken].newToken;
     uint256 oldDecimals = _tokenPairs[oldToken].oldTokenDecimals;
     uint256 newDecimals = _tokenPairs[oldToken].newTokenDecimals;
     if (newToken == address(0)) return (address(0), 0);
-    newAmount = (amount * 10**newDecimals) / 10**oldDecimals;
+    if (newDecimals >= oldDecimals) {
+        newAmount = amount * (10**(newDecimals - oldDecimals));
+    } else {
+        newAmount = amount / (10**(oldDecimals - newDecimals));
+    }
 }
📝 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
function previewMigrate(address oldToken, uint256 amount)
public
view
returns (address newToken, uint256 newAmount)
{
newToken = _tokenPairs[oldToken].newToken;
uint256 oldDecimals = _tokenPairs[oldToken].oldTokenDecimals;
uint256 newDecimals = _tokenPairs[oldToken].newTokenDecimals;
if (newToken == address(0)) return (address(0), 0);
newAmount = (amount * 10**newDecimals) / 10**oldDecimals;
}
function previewMigrate(address oldToken, uint256 amount)
public
view
returns (address newToken, uint256 newAmount)
{
newToken = _tokenPairs[oldToken].newToken;
uint256 oldDecimals = _tokenPairs[oldToken].oldTokenDecimals;
uint256 newDecimals = _tokenPairs[oldToken].newTokenDecimals;
if (newToken == address(0)) return (address(0), 0);
if (newDecimals >= oldDecimals) {
newAmount = amount * (10**(newDecimals - oldDecimals));
} else {
newAmount = amount / (10**(oldDecimals - newDecimals));
}
}
🤖 Prompt for AI Agents
In contracts/migrator/SynapseMigrator.sol around lines 64 to 74, the decimal
conversion calculation multiplies amount by 10 to the power of newDecimals,
which can overflow for large values. To fix this, use a safe math approach such
as performing the multiplication and division in a way that avoids intermediate
overflow, for example by dividing first if possible or using a library like
SafeMath or unchecked blocks with explicit overflow checks. Ensure the
calculation safely handles large amounts and decimal differences without risking
overflow.

Comment on lines +46 to +56
function migrate(address oldToken, uint256 amount) external {
if (oldToken == address(0)) revert SM__ZeroAddress();
(address newToken, uint256 newAmount) = previewMigrate(oldToken, amount);
if (newToken == address(0)) revert SM__TokenPairNotAdded();
if (newAmount == 0) revert SM__ZeroAmount();
// Burn old tokens from the user
IBurnable(oldToken).burnFrom(msg.sender, amount);
// Send new tokens to the user
IERC20(newToken).safeTransfer(msg.sender, newAmount);
emit Migrated(msg.sender, oldToken, amount);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Migration function has potential reentrancy risk.

The function calls external contracts (burn and transfer) which could potentially lead to reentrancy attacks. Consider adding a reentrancy guard.

+import {ReentrancyGuard} from "@openzeppelin/contracts-4.5.0/security/ReentrancyGuard.sol";

-contract SynapseMigrator is Ownable, ISynapseMigrator, ISynapseMigratorErrors {
+contract SynapseMigrator is Ownable, ReentrancyGuard, ISynapseMigrator, ISynapseMigratorErrors {

-    function migrate(address oldToken, uint256 amount) external {
+    function migrate(address oldToken, uint256 amount) external nonReentrant {
📝 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
function migrate(address oldToken, uint256 amount) external {
if (oldToken == address(0)) revert SM__ZeroAddress();
(address newToken, uint256 newAmount) = previewMigrate(oldToken, amount);
if (newToken == address(0)) revert SM__TokenPairNotAdded();
if (newAmount == 0) revert SM__ZeroAmount();
// Burn old tokens from the user
IBurnable(oldToken).burnFrom(msg.sender, amount);
// Send new tokens to the user
IERC20(newToken).safeTransfer(msg.sender, newAmount);
emit Migrated(msg.sender, oldToken, amount);
}
function migrate(address oldToken, uint256 amount) external nonReentrant {
if (oldToken == address(0)) revert SM__ZeroAddress();
(address newToken, uint256 newAmount) = previewMigrate(oldToken, amount);
if (newToken == address(0)) revert SM__TokenPairNotAdded();
if (newAmount == 0) revert SM__ZeroAmount();
// Burn old tokens from the user
IBurnable(oldToken).burnFrom(msg.sender, amount);
// Send new tokens to the user
IERC20(newToken).safeTransfer(msg.sender, newAmount);
emit Migrated(msg.sender, oldToken, amount);
}
🤖 Prompt for AI Agents
In contracts/migrator/SynapseMigrator.sol around lines 46 to 56, the migrate
function calls external contracts for burning and transferring tokens without
protection against reentrancy attacks. To fix this, add a reentrancy guard
modifier to the migrate function and apply the checks-effects-interactions
pattern by performing all state changes before external calls. This will prevent
potential reentrancy exploits during token operations.

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.

2 participants