From 9bb3cbb4edfbaf407d583bb437afbf347806746e Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Thu, 5 Jun 2025 15:27:04 +0100 Subject: [PATCH 1/6] feat: scaffold the migrator contract --- contracts/migrator/SynapseMigrator.sol | 32 +++++++++++++++++++ .../migrator/interfaces/ISynapseMigrator.sol | 29 +++++++++++++++++ .../interfaces/ISynapseMigratorErrors.sol | 10 ++++++ 3 files changed, 71 insertions(+) create mode 100644 contracts/migrator/SynapseMigrator.sol create mode 100644 contracts/migrator/interfaces/ISynapseMigrator.sol create mode 100644 contracts/migrator/interfaces/ISynapseMigratorErrors.sol diff --git a/contracts/migrator/SynapseMigrator.sol b/contracts/migrator/SynapseMigrator.sol new file mode 100644 index 000000000..5494a6ca3 --- /dev/null +++ b/contracts/migrator/SynapseMigrator.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {ISynapseMigrator} from "./interfaces/ISynapseMigrator.sol"; +import {ISynapseMigratorErrors} from "./interfaces/ISynapseMigratorErrors.sol"; + +import {Ownable} from "@openzeppelin/contracts-4.5.0/access/Ownable.sol"; + +contract SynapseMigrator is Ownable, ISynapseMigrator, ISynapseMigratorErrors { + event Migrated(address indexed user, address indexed oldToken, uint256 amount); + event TokenPairAdded(address indexed oldToken, address indexed newToken); + + /// @inheritdoc ISynapseMigrator + function addTokenPair(address oldToken, address newToken) external onlyOwner { + // TODO: implement + } + + /// @inheritdoc ISynapseMigrator + function migrate(address oldToken, uint256 amount) external { + // TODO: implement + } + + /// @inheritdoc ISynapseMigrator + function getTokenPair(address oldToken) external view returns (address newToken) { + // TODO: implement + } + + /// @inheritdoc ISynapseMigrator + function previewMigrate(address oldToken, uint256 amount) external view returns (uint256 newAmount) { + // TODO: implement + } +} diff --git a/contracts/migrator/interfaces/ISynapseMigrator.sol b/contracts/migrator/interfaces/ISynapseMigrator.sol new file mode 100644 index 000000000..3a7f4547f --- /dev/null +++ b/contracts/migrator/interfaces/ISynapseMigrator.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +interface ISynapseMigrator { + /// @notice Allows the contract owner to add a token pair to the migrator. + /// Users will be able to migrate from the old token to the new token using 1:1 ratio, + /// taking token decimals into account. + /// @dev Will revert in the following cases: + /// - Either of the tokens is the zero address. + /// - Token addresses are the same. + /// - The token pair is already added for the old token. + function addTokenPair(address oldToken, address newToken) external; + + /// @notice Migrates the given amount of old tokens to new tokens. + /// Old tokens will be taken from the user and burned. New tokens will be transferred to the user. + /// @dev Will revert in the following cases: + /// - Zero address or amount is supplied. + /// - The token pair is not added for the old token. + /// - Contract does not have enough balance of the new token. + function migrate(address oldToken, uint256 amount) external; + + /// @notice Returns the new token for the given old token. + /// @dev Will return the zero address if the token pair is not added for the old token. + function getTokenPair(address oldToken) external view returns (address newToken); + + /// @notice Returns the amount of new tokens that will be received for the given amount of old tokens. + /// @dev Will return 0 if the token pair is not added for the old token. + function previewMigrate(address oldToken, uint256 amount) external view returns (uint256 newAmount); +} diff --git a/contracts/migrator/interfaces/ISynapseMigratorErrors.sol b/contracts/migrator/interfaces/ISynapseMigratorErrors.sol new file mode 100644 index 000000000..b78d34345 --- /dev/null +++ b/contracts/migrator/interfaces/ISynapseMigratorErrors.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +interface ISynapseMigratorErrors { + error SM__SameAddress(); + error SM__TokenPairAlreadyAdded(); + error SM__TokenPairNotAdded(); + error SM__ZeroAddress(); + error SM__ZeroAmount(); +} From ad46eedafd2baa67866fdc8bb6671ef4fc0397ea Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Thu, 5 Jun 2025 15:52:00 +0100 Subject: [PATCH 2/6] test: add management tests --- contracts/migrator/SynapseMigrator.sol | 4 + .../migrator/SynapseMigrator.Management.t.sol | 97 +++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 test/migrator/SynapseMigrator.Management.t.sol diff --git a/contracts/migrator/SynapseMigrator.sol b/contracts/migrator/SynapseMigrator.sol index 5494a6ca3..c7b6d8c80 100644 --- a/contracts/migrator/SynapseMigrator.sol +++ b/contracts/migrator/SynapseMigrator.sol @@ -10,6 +10,10 @@ contract SynapseMigrator is Ownable, ISynapseMigrator, ISynapseMigratorErrors { event Migrated(address indexed user, address indexed oldToken, uint256 amount); event TokenPairAdded(address indexed oldToken, address indexed newToken); + constructor(address owner_) { + transferOwnership(owner_); + } + /// @inheritdoc ISynapseMigrator function addTokenPair(address oldToken, address newToken) external onlyOwner { // TODO: implement diff --git a/test/migrator/SynapseMigrator.Management.t.sol b/test/migrator/SynapseMigrator.Management.t.sol new file mode 100644 index 000000000..9cde90341 --- /dev/null +++ b/test/migrator/SynapseMigrator.Management.t.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {SynapseMigrator, ISynapseMigratorErrors} from "../../contracts/migrator/SynapseMigrator.sol"; + +import {IERC20Metadata} from "@openzeppelin/contracts-4.5.0/token/ERC20/extensions/IERC20Metadata.sol"; + +import {Test} from "forge-std/Test.sol"; + +// solhint-disable func-name-mixedcase +contract SynapseMigratorManagementTest is Test, ISynapseMigratorErrors { + event TokenPairAdded(address indexed oldToken, address indexed newToken); + + SynapseMigrator internal migrator; + address internal owner = makeAddr("owner"); + + address internal oldToken = makeAddr("oldToken"); + address internal newToken = makeAddr("newToken"); + address internal anotherToken = makeAddr("anotherToken"); + + function setUp() public { + migrator = new SynapseMigrator(owner); + + vm.mockCall({ + callee: oldToken, + data: abi.encodeWithSelector(IERC20Metadata.decimals.selector), + returnData: abi.encode(18) + }); + vm.mockCall({ + callee: newToken, + data: abi.encodeWithSelector(IERC20Metadata.decimals.selector), + returnData: abi.encode(6) + }); + vm.mockCall({ + callee: anotherToken, + data: abi.encodeWithSelector(IERC20Metadata.decimals.selector), + returnData: abi.encode(6) + }); + } + + function test_constructor() public { + assertEq(migrator.owner(), owner); + assertEq(migrator.getTokenPair(oldToken), address(0)); + } + + function test_constructor_revert_zeroOwner() public { + vm.expectRevert("Ownable: new owner is the zero address"); + new SynapseMigrator(address(0)); + } + + function test_addTokenPair() public { + vm.expectEmit(address(migrator)); + emit TokenPairAdded(oldToken, newToken); + vm.prank(owner); + migrator.addTokenPair(oldToken, newToken); + assertEq(migrator.getTokenPair(oldToken), newToken); + assertEq(migrator.getTokenPair(newToken), address(0)); + } + + function test_addTokenPair_revert_oldTokenZero() public { + vm.expectRevert(SM__ZeroAddress.selector); + vm.prank(owner); + migrator.addTokenPair(address(0), newToken); + } + + function test_addTokenPair_revert_newTokenZero() public { + vm.expectRevert(SM__ZeroAddress.selector); + vm.prank(owner); + migrator.addTokenPair(oldToken, address(0)); + } + + function test_addTokenPair_revert_sameTokens() public { + vm.expectRevert(SM__SameAddress.selector); + vm.prank(owner); + migrator.addTokenPair(oldToken, oldToken); + } + + function test_addTokenPair_revert_oldTokenAlreadyAdded() public { + vm.prank(owner); + migrator.addTokenPair(oldToken, newToken); + // Same address + vm.expectRevert(SM__TokenPairAlreadyAdded.selector); + vm.prank(owner); + migrator.addTokenPair(oldToken, newToken); + // New address + vm.expectRevert(SM__TokenPairAlreadyAdded.selector); + vm.prank(owner); + migrator.addTokenPair(oldToken, anotherToken); + } + + function test_addTokenPair_revert_callerNotOwner(address caller) public { + vm.assume(caller != owner); + vm.expectRevert("Ownable: caller is not the owner"); + vm.prank(caller); + migrator.addTokenPair(oldToken, newToken); + } +} From 14c7254409a95ddc04e5057391fec5adf067214e Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Thu, 5 Jun 2025 16:16:00 +0100 Subject: [PATCH 3/6] test: migration cases --- .../SynapseMigrator.LessDecimals.t.sol | 32 ++++++ .../SynapseMigrator.MoreDecimals.t.sol | 8 ++ .../SynapseMigrator.SameDecimals.t.sol | 8 ++ test/migrator/SynapseMigrator.t.sol | 104 ++++++++++++++++++ test/mocks/MockBurnableToken.sol | 25 +++++ 5 files changed, 177 insertions(+) create mode 100644 test/migrator/SynapseMigrator.LessDecimals.t.sol create mode 100644 test/migrator/SynapseMigrator.MoreDecimals.t.sol create mode 100644 test/migrator/SynapseMigrator.SameDecimals.t.sol create mode 100644 test/migrator/SynapseMigrator.t.sol create mode 100644 test/mocks/MockBurnableToken.sol diff --git a/test/migrator/SynapseMigrator.LessDecimals.t.sol b/test/migrator/SynapseMigrator.LessDecimals.t.sol new file mode 100644 index 000000000..b7df0b7ee --- /dev/null +++ b/test/migrator/SynapseMigrator.LessDecimals.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {SynapseMigratorTest} from "./SynapseMigrator.t.sol"; + +contract SynapseMigratorLessDecimalsTest is SynapseMigratorTest { + constructor() SynapseMigratorTest(18, 6) {} + + function test_migrate_precisionLoss() public { + amount = 1.23456789 * 10**18; + expectedNewAmount = 1234567; + test_migrate(); + } + + function test_migrate_revert_precisionLoss_zeroAmountOut() public { + vm.expectRevert(SM__ZeroAmount.selector); + vm.prank(user); + migrator.migrate(address(oldToken), 10**12 - 1); + } + + function test_previewMigrate_precisionLoss() public { + amount = 1.23456789 * 10**18; + expectedNewAmount = 1234567; + test_previewMigrate(); + } + + function test_previewMigrate_precisionLoss_zeroAmountOut() public { + amount = 10**12 - 1; + expectedNewAmount = 0; + test_previewMigrate(); + } +} diff --git a/test/migrator/SynapseMigrator.MoreDecimals.t.sol b/test/migrator/SynapseMigrator.MoreDecimals.t.sol new file mode 100644 index 000000000..410a4bee6 --- /dev/null +++ b/test/migrator/SynapseMigrator.MoreDecimals.t.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {SynapseMigratorTest} from "./SynapseMigrator.t.sol"; + +contract SynapseMigratorMoreDecimalsTest is SynapseMigratorTest { + constructor() SynapseMigratorTest(6, 18) {} +} diff --git a/test/migrator/SynapseMigrator.SameDecimals.t.sol b/test/migrator/SynapseMigrator.SameDecimals.t.sol new file mode 100644 index 000000000..1f9bb34ea --- /dev/null +++ b/test/migrator/SynapseMigrator.SameDecimals.t.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {SynapseMigratorTest} from "./SynapseMigrator.t.sol"; + +contract SynapseMigratorSameDecimalsTest is SynapseMigratorTest { + constructor() SynapseMigratorTest(18, 18) {} +} diff --git a/test/migrator/SynapseMigrator.t.sol b/test/migrator/SynapseMigrator.t.sol new file mode 100644 index 000000000..47e5e1759 --- /dev/null +++ b/test/migrator/SynapseMigrator.t.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {SynapseMigrator, ISynapseMigratorErrors} from "../../contracts/migrator/SynapseMigrator.sol"; + +import {MockBurnableToken} from "../mocks/MockBurnableToken.sol"; + +import {Test} from "forge-std/Test.sol"; + +abstract contract SynapseMigratorTest is Test, ISynapseMigratorErrors { + event Migrated(address indexed user, address indexed oldToken, uint256 amount); + + SynapseMigrator internal migrator; + MockBurnableToken internal oldToken; + MockBurnableToken internal newToken; + + uint8 internal oldTokenDecimals; + uint8 internal newTokenDecimals; + + address internal user = makeAddr("user"); + uint256 internal oldTokenBalance; + uint256 internal newTokenSupply; + uint256 internal amount; + uint256 internal expectedNewAmount; + + constructor(uint8 oldTokenDecimals_, uint8 newTokenDecimals_) { + oldTokenDecimals = oldTokenDecimals_; + newTokenDecimals = newTokenDecimals_; + } + + function setUp() public { + migrator = new SynapseMigrator(address(this)); + oldToken = new MockBurnableToken("OldToken", oldTokenDecimals); + newToken = new MockBurnableToken("NewToken", newTokenDecimals); + migrator.addTokenPair(address(oldToken), address(newToken)); + + // 10 tokens + oldTokenBalance = 10 * 10**oldTokenDecimals; + newTokenSupply = 10 * 10**newTokenDecimals; + // Migrate 1 token + amount = 10**oldTokenDecimals; + expectedNewAmount = 10**newTokenDecimals; + + oldToken.mintTestTokens(user, oldTokenBalance); + newToken.mintTestTokens(address(migrator), newTokenSupply); + + vm.prank(user); + oldToken.approve(address(migrator), type(uint256).max); + } + + function test_migrate() public { + vm.expectEmit(address(migrator)); + emit Migrated(user, address(oldToken), amount); + vm.prank(user); + migrator.migrate(address(oldToken), amount); + // Old token balances + assertEq(oldToken.balanceOf(user), oldTokenBalance - amount); + assertEq(oldToken.balanceOf(address(migrator)), 0); + assertEq(oldToken.totalSupply(), oldTokenBalance - amount); + // New token balances + assertEq(newToken.balanceOf(user), expectedNewAmount); + assertEq(newToken.balanceOf(address(migrator)), newTokenSupply - expectedNewAmount); + assertEq(newToken.totalSupply(), newTokenSupply); + } + + function test_migrate_revert_oldTokenNotAdded() public { + // Redeploy migrator to effectively remove the token pair + migrator = new SynapseMigrator(address(this)); + vm.expectRevert(SM__TokenPairNotAdded.selector); + vm.prank(user); + migrator.migrate(address(oldToken), amount); + } + + function test_migrate_revert_oldTokenZero() public { + vm.expectRevert(SM__ZeroAddress.selector); + vm.prank(user); + migrator.migrate(address(0), amount); + } + + function test_migrate_revert_amountZero() public { + vm.expectRevert(SM__ZeroAmount.selector); + vm.prank(user); + migrator.migrate(address(oldToken), 0); + } + + function test_migrate_revert_notEnoughAllowance() public { + vm.prank(user); + oldToken.approve(address(migrator), amount - 1); + vm.expectRevert(); + vm.prank(user); + migrator.migrate(address(oldToken), amount); + } + + function test_previewMigrate() public { + uint256 previewedAmount = migrator.previewMigrate(address(oldToken), amount); + assertEq(previewedAmount, expectedNewAmount); + } + + function test_previewMigrate_returnsZero_tokenNotAdded() public { + migrator = new SynapseMigrator(address(this)); + uint256 previewedAmount = migrator.previewMigrate(address(oldToken), amount); + assertEq(previewedAmount, 0); + } +} diff --git a/test/mocks/MockBurnableToken.sol b/test/mocks/MockBurnableToken.sol new file mode 100644 index 000000000..1985c83a4 --- /dev/null +++ b/test/mocks/MockBurnableToken.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {ERC20, ERC20Burnable} from "@openzeppelin/contracts-4.5.0/token/ERC20/extensions/ERC20Burnable.sol"; + +// solhint-disable no-empty-blocks +/// @notice Obviously, do NOT use this token in production. It's only for testing purposes. +contract MockBurnableToken is ERC20Burnable { + uint8 private _decimals; + + constructor(string memory name_, uint8 decimals_) ERC20(name_, name_) { + _decimals = decimals_; + } + + /// @notice We include an empty "test" function so that this contract does not appear in the coverage report. + function testMockBurnableToken() external {} + + function decimals() public view virtual override returns (uint8) { + return _decimals; + } + + function mintTestTokens(address to, uint256 amount) external { + _mint(to, amount); + } +} From b68d3602a17e88a411bc02abd9991856064c6fae Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Thu, 5 Jun 2025 16:16:31 +0100 Subject: [PATCH 4/6] feat: migrator --- contracts/migrator/SynapseMigrator.sol | 54 +++++++++++++++++++-- contracts/migrator/interfaces/IBurnable.sol | 6 +++ 2 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 contracts/migrator/interfaces/IBurnable.sol diff --git a/contracts/migrator/SynapseMigrator.sol b/contracts/migrator/SynapseMigrator.sol index c7b6d8c80..1a8c9c688 100644 --- a/contracts/migrator/SynapseMigrator.sol +++ b/contracts/migrator/SynapseMigrator.sol @@ -1,12 +1,25 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.17; +import {IBurnable} from "./interfaces/IBurnable.sol"; import {ISynapseMigrator} from "./interfaces/ISynapseMigrator.sol"; import {ISynapseMigratorErrors} from "./interfaces/ISynapseMigratorErrors.sol"; import {Ownable} from "@openzeppelin/contracts-4.5.0/access/Ownable.sol"; +import {IERC20Metadata} from "@openzeppelin/contracts-4.5.0/token/ERC20/extensions/IERC20Metadata.sol"; +import {SafeERC20, IERC20} from "@openzeppelin/contracts-4.5.0/token/ERC20/utils/SafeERC20.sol"; contract SynapseMigrator is Ownable, ISynapseMigrator, ISynapseMigratorErrors { + using SafeERC20 for IERC20; + + struct TokenPair { + address newToken; + uint8 oldTokenDecimals; + uint8 newTokenDecimals; + } + + mapping(address => TokenPair) internal _tokenPairs; + event Migrated(address indexed user, address indexed oldToken, uint256 amount); event TokenPairAdded(address indexed oldToken, address indexed newToken); @@ -16,21 +29,52 @@ contract SynapseMigrator is Ownable, ISynapseMigrator, ISynapseMigratorErrors { /// @inheritdoc ISynapseMigrator function addTokenPair(address oldToken, address newToken) external onlyOwner { - // TODO: implement + if (oldToken == address(0) || newToken == address(0)) revert SM__ZeroAddress(); + if (oldToken == newToken) revert SM__SameAddress(); + // Check that the token pair has not been added yet + if (_tokenPairs[oldToken].newToken != address(0)) revert SM__TokenPairAlreadyAdded(); + // Add token pair and record the tokens decimals to avoid extra calls in the future + _tokenPairs[oldToken] = TokenPair({ + newToken: newToken, + oldTokenDecimals: IERC20Metadata(oldToken).decimals(), + newTokenDecimals: IERC20Metadata(newToken).decimals() + }); + emit TokenPairAdded(oldToken, newToken); } /// @inheritdoc ISynapseMigrator function migrate(address oldToken, uint256 amount) external { - // TODO: implement + 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); } /// @inheritdoc ISynapseMigrator function getTokenPair(address oldToken) external view returns (address newToken) { - // TODO: implement + return _tokenPairs[oldToken].newToken; } /// @inheritdoc ISynapseMigrator - function previewMigrate(address oldToken, uint256 amount) external view returns (uint256 newAmount) { - // TODO: implement + function previewMigrate(address oldToken, uint256 amount) public view returns (uint256 newAmount) { + (, newAmount) = _previewMigrate(oldToken, amount); + } + + /// @dev Internal function to preview the amount of new tokens that will be received + function _previewMigrate(address oldToken, uint256 amount) + internal + 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; } } diff --git a/contracts/migrator/interfaces/IBurnable.sol b/contracts/migrator/interfaces/IBurnable.sol new file mode 100644 index 000000000..49f3d6a9f --- /dev/null +++ b/contracts/migrator/interfaces/IBurnable.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +interface IBurnable { + function burnFrom(address from, uint256 amount) external; +} From 83b362e9b1486c7b47c2b89396543df4d86777c5 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Mon, 9 Jun 2025 18:42:57 +0100 Subject: [PATCH 5/6] refactor: enhance previewMigrate to return both token and amount MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- contracts/migrator/SynapseMigrator.sol | 11 +++-------- contracts/migrator/interfaces/ISynapseMigrator.sol | 9 ++++++--- test/migrator/SynapseMigrator.t.sol | 6 ++++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/contracts/migrator/SynapseMigrator.sol b/contracts/migrator/SynapseMigrator.sol index 1a8c9c688..9e0d1a778 100644 --- a/contracts/migrator/SynapseMigrator.sol +++ b/contracts/migrator/SynapseMigrator.sol @@ -45,7 +45,7 @@ contract SynapseMigrator is Ownable, ISynapseMigrator, ISynapseMigratorErrors { /// @inheritdoc ISynapseMigrator function migrate(address oldToken, uint256 amount) external { if (oldToken == address(0)) revert SM__ZeroAddress(); - (address newToken, uint256 newAmount) = _previewMigrate(oldToken, amount); + (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 @@ -61,13 +61,8 @@ contract SynapseMigrator is Ownable, ISynapseMigrator, ISynapseMigratorErrors { } /// @inheritdoc ISynapseMigrator - function previewMigrate(address oldToken, uint256 amount) public view returns (uint256 newAmount) { - (, newAmount) = _previewMigrate(oldToken, amount); - } - - /// @dev Internal function to preview the amount of new tokens that will be received - function _previewMigrate(address oldToken, uint256 amount) - internal + function previewMigrate(address oldToken, uint256 amount) + public view returns (address newToken, uint256 newAmount) { diff --git a/contracts/migrator/interfaces/ISynapseMigrator.sol b/contracts/migrator/interfaces/ISynapseMigrator.sol index 3a7f4547f..4c136a3d2 100644 --- a/contracts/migrator/interfaces/ISynapseMigrator.sol +++ b/contracts/migrator/interfaces/ISynapseMigrator.sol @@ -23,7 +23,10 @@ interface ISynapseMigrator { /// @dev Will return the zero address if the token pair is not added for the old token. function getTokenPair(address oldToken) external view returns (address newToken); - /// @notice Returns the amount of new tokens that will be received for the given amount of old tokens. - /// @dev Will return 0 if the token pair is not added for the old token. - function previewMigrate(address oldToken, uint256 amount) external view returns (uint256 newAmount); + /// @notice Returns the new token and amount of new tokens that will be received for the given amount of old tokens. + /// @dev Will return (address(0), 0) if the token pair is not added for the old token. + function previewMigrate(address oldToken, uint256 amount) + external + view + returns (address newToken, uint256 newAmount); } diff --git a/test/migrator/SynapseMigrator.t.sol b/test/migrator/SynapseMigrator.t.sol index 47e5e1759..37a4fc142 100644 --- a/test/migrator/SynapseMigrator.t.sol +++ b/test/migrator/SynapseMigrator.t.sol @@ -92,13 +92,15 @@ abstract contract SynapseMigratorTest is Test, ISynapseMigratorErrors { } function test_previewMigrate() public { - uint256 previewedAmount = migrator.previewMigrate(address(oldToken), amount); + (address previewedNewToken, uint256 previewedAmount) = migrator.previewMigrate(address(oldToken), amount); + assertEq(previewedNewToken, address(newToken)); assertEq(previewedAmount, expectedNewAmount); } function test_previewMigrate_returnsZero_tokenNotAdded() public { migrator = new SynapseMigrator(address(this)); - uint256 previewedAmount = migrator.previewMigrate(address(oldToken), amount); + (address previewedNewToken, uint256 previewedAmount) = migrator.previewMigrate(address(oldToken), amount); + assertEq(previewedNewToken, address(0)); assertEq(previewedAmount, 0); } } From 8a7aa0523e31aef806660df79bbf4b74f29dc81e Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Mon, 9 Jun 2025 18:44:09 +0100 Subject: [PATCH 6/6] chore: silence solhint warnings in tests --- test/migrator/SynapseMigrator.LessDecimals.t.sol | 1 + test/migrator/SynapseMigrator.t.sol | 1 + 2 files changed, 2 insertions(+) diff --git a/test/migrator/SynapseMigrator.LessDecimals.t.sol b/test/migrator/SynapseMigrator.LessDecimals.t.sol index b7df0b7ee..5fcceb506 100644 --- a/test/migrator/SynapseMigrator.LessDecimals.t.sol +++ b/test/migrator/SynapseMigrator.LessDecimals.t.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.17; import {SynapseMigratorTest} from "./SynapseMigrator.t.sol"; +// solhint-disable func-name-mixedcase contract SynapseMigratorLessDecimalsTest is SynapseMigratorTest { constructor() SynapseMigratorTest(18, 6) {} diff --git a/test/migrator/SynapseMigrator.t.sol b/test/migrator/SynapseMigrator.t.sol index 37a4fc142..d21db133f 100644 --- a/test/migrator/SynapseMigrator.t.sol +++ b/test/migrator/SynapseMigrator.t.sol @@ -7,6 +7,7 @@ import {MockBurnableToken} from "../mocks/MockBurnableToken.sol"; import {Test} from "forge-std/Test.sol"; +// solhint-disable func-name-mixedcase abstract contract SynapseMigratorTest is Test, ISynapseMigratorErrors { event Migrated(address indexed user, address indexed oldToken, uint256 amount);