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

Smr 1814 Part 2 imx withdraw on L1 #22

Merged
merged 5 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions src/root/RootERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ contract RootERC20Bridge is
bytes32 public constant DEPOSIT_SIG = keccak256("DEPOSIT");
bytes32 public constant WITHDRAW_SIG = keccak256("WITHDRAW");
address public constant NATIVE_ETH = address(0xeee);
address public constant NATIVE_IMX = address(0xfff);

IRootERC20BridgeAdaptor public rootBridgeAdaptor;
/// @dev Used to verify source address in messages sent from child chain.
Expand Down Expand Up @@ -352,9 +353,14 @@ contract RootERC20Bridge is
function _withdraw(bytes memory data) private {
(address rootToken, address withdrawer, address receiver, uint256 amount) =
abi.decode(data, (address, address, address, uint256));
address childToken = rootTokenToChildToken[rootToken];
if (childToken == address(0)) {
revert NotMapped();
address childToken;
if (address(rootToken) == rootIMXToken) {
childToken = NATIVE_IMX;
} else {
childToken = rootTokenToChildToken[rootToken];
if (childToken == address(0)) {
revert NotMapped();
}
}
_executeTransfer(rootToken, childToken, withdrawer, receiver, amount);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ contract RootERC20BridgeWithdrawIntegrationTest is
address constant IMX_TOKEN_ADDRESS = address(0xccc);
address constant NATIVE_ETH = address(0xeee);
address constant WRAPPED_ETH = address(0xddd);
address public constant NATIVE_IMX = address(0xfff);
uint256 constant UNLIMITED_IMX_DEPOSIT_LIMIT = 0;

uint256 constant withdrawAmount = 0.5 ether;
Expand Down Expand Up @@ -59,6 +60,7 @@ contract RootERC20BridgeWithdrawIntegrationTest is
rootBridge.mapToken{value: 1}(token);
// And give the bridge some tokens
token.transfer(address(rootBridge), 100 ether);
imxToken.transfer(address(rootBridge), 100 ether);
}

function test_RevertsIf_WithdrawWithInvalidSourceAddress() public {
Expand Down Expand Up @@ -119,6 +121,24 @@ contract RootERC20BridgeWithdrawIntegrationTest is
assertEq(bridgePostBal, bridgePreBal - withdrawAmount, "Incorrect bridge balance after withdraw");
}

function test_withdrawIMX_TransfersIMX() public {
bytes memory data = abi.encode(WITHDRAW_SIG, IMX_TOKEN_ADDRESS, address(this), address(this), withdrawAmount);

bytes32 commandId = bytes32("testCommandId");
string memory sourceAddress = rootBridge.childBridgeAdaptor();

uint256 thisPreBal = imxToken.balanceOf(address(this));
uint256 bridgePreBal = imxToken.balanceOf(address(rootBridge));

axelarAdaptor.execute(commandId, CHILD_CHAIN_NAME, sourceAddress, data);

uint256 thisPostBal = imxToken.balanceOf(address(this));
uint256 bridgePostBal = imxToken.balanceOf(address(rootBridge));

assertEq(thisPostBal, thisPreBal + withdrawAmount, "Incorrect user balance after withdraw");
assertEq(bridgePostBal, bridgePreBal - withdrawAmount, "Incorrect bridge balance after withdraw");
}

function test_withdraw_TransfersTokens_DifferentReceiver() public {
address receiver = address(987654321);
bytes memory data = abi.encode(WITHDRAW_SIG, address(token), address(this), receiver, withdrawAmount);
Expand All @@ -138,6 +158,25 @@ contract RootERC20BridgeWithdrawIntegrationTest is
assertEq(bridgePostBal, bridgePreBal - withdrawAmount, "Incorrect bridge balance after withdraw");
}

function test_withdrawIMX_TransfersIMX_DifferentReceiver() public {
address receiver = address(987654321);
bytes memory data = abi.encode(WITHDRAW_SIG, IMX_TOKEN_ADDRESS, address(this), receiver, withdrawAmount);

bytes32 commandId = bytes32("testCommandId");
string memory sourceAddress = rootBridge.childBridgeAdaptor();

uint256 thisPreBal = imxToken.balanceOf(receiver);
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: thisPreBal is a bit misleading on this test, since it is the receiver's balance not this contract's balance like the previous test

uint256 bridgePreBal = imxToken.balanceOf(address(rootBridge));

axelarAdaptor.execute(commandId, CHILD_CHAIN_NAME, sourceAddress, data);

uint256 thisPostBal = imxToken.balanceOf(receiver);
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: thisPostBal is a bit misleading on this test, since it is the receiver's balance not this contract's balance like the previous test

uint256 bridgePostBal = imxToken.balanceOf(address(rootBridge));

assertEq(thisPostBal, thisPreBal + withdrawAmount, "Incorrect user balance after withdraw");
assertEq(bridgePostBal, bridgePreBal - withdrawAmount, "Incorrect bridge balance after withdraw");
}

function test_withdraw_EmitsRootChainERC20WithdrawEvent() public {
bytes memory data = abi.encode(WITHDRAW_SIG, address(token), address(this), address(this), withdrawAmount);

Expand All @@ -155,6 +194,17 @@ contract RootERC20BridgeWithdrawIntegrationTest is
axelarAdaptor.execute(commandId, CHILD_CHAIN_NAME, sourceAddress, data);
}

function test_withdrawIMX_EmitsRootChainERC20WithdrawEvent() public {
bytes memory data = abi.encode(WITHDRAW_SIG, IMX_TOKEN_ADDRESS, address(this), address(this), withdrawAmount);

bytes32 commandId = bytes32("testCommandId");
string memory sourceAddress = rootBridge.childBridgeAdaptor();

vm.expectEmit();
emit RootChainERC20Withdraw(address(imxToken), NATIVE_IMX, address(this), address(this), withdrawAmount);
axelarAdaptor.execute(commandId, CHILD_CHAIN_NAME, sourceAddress, data);
}

function test_withdraw_EmitsRootChainERC20WithdrawEvent_DifferentReceiver() public {
address receiver = address(987654321);
bytes memory data = abi.encode(WITHDRAW_SIG, address(token), address(this), receiver, withdrawAmount);
Expand All @@ -168,4 +218,16 @@ contract RootERC20BridgeWithdrawIntegrationTest is
);
axelarAdaptor.execute(commandId, CHILD_CHAIN_NAME, sourceAddress, data);
}

function test_withdrawIMX_EmitsRootChainERC20WithdrawEvent_DifferentReceiver() public {
address receiver = address(987654321);
bytes memory data = abi.encode(WITHDRAW_SIG, IMX_TOKEN_ADDRESS, address(this), receiver, withdrawAmount);

bytes32 commandId = bytes32("testCommandId");
string memory sourceAddress = rootBridge.childBridgeAdaptor();

vm.expectEmit();
emit RootChainERC20Withdraw(address(imxToken), NATIVE_IMX, address(this), receiver, withdrawAmount);
axelarAdaptor.execute(commandId, CHILD_CHAIN_NAME, sourceAddress, data);
}
}
73 changes: 68 additions & 5 deletions test/unit/root/withdrawals/RootERC20BridgeWithdraw.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ contract RootERC20BridgeWithdrawUnitTest is Test, IRootERC20BridgeEvents, IRootE
address constant CHILD_BRIDGE_ADAPTOR = address(4);
string CHILD_BRIDGE_ADAPTOR_STRING = Strings.toHexString(CHILD_BRIDGE_ADAPTOR);
string constant CHILD_CHAIN_NAME = "test";
address constant UnmappedToken = address(0xbbb);
address constant IMX_TOKEN = address(0xccc);
address constant WRAPPED_ETH = address(0xddd);
address public constant NATIVE_IMX = address(0xfff);
uint256 constant mapTokenFee = 300;
uint256 constant withdrawAmount = 0.5 ether;
uint256 constant UNLIMITED_IMX_DEPOSIT_LIMIT = 0;

ERC20PresetMinterPauser public token;
ERC20PresetMinterPauser public imxToken;
RootERC20Bridge public rootBridge;
MockAdaptor public mockAxelarAdaptor;
MockAxelarGateway public mockAxelarGateway;
Expand All @@ -33,6 +36,8 @@ contract RootERC20BridgeWithdrawUnitTest is Test, IRootERC20BridgeEvents, IRootE
token = new ERC20PresetMinterPauser("Test", "TST");
token.mint(address(this), 100 ether);
deployCodeTo("ERC20PresetMinterPauser.sol", abi.encode("ImmutableX", "IMX"), IMX_TOKEN);
imxToken = ERC20PresetMinterPauser(IMX_TOKEN);
imxToken.mint(address(this), 100 ether);

deployCodeTo("WETH.sol", abi.encode("Wrapped ETH", "WETH"), WRAPPED_ETH);

Expand All @@ -56,22 +61,22 @@ contract RootERC20BridgeWithdrawUnitTest is Test, IRootERC20BridgeEvents, IRootE
}

function test_RevertsIf_WithdrawWithInvalidSender() public {
bytes memory data = abi.encode(WITHDRAW_SIG, IMX_TOKEN, address(this), address(this), withdrawAmount);
bytes memory data = abi.encode(WITHDRAW_SIG, token, address(this), address(this), withdrawAmount);

vm.expectRevert(NotBridgeAdaptor.selector);
rootBridge.onMessageReceive(CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, data);
}

function test_RevertsIf_OnMessageReceiveWithInvalidSourceChain() public {
bytes memory data = abi.encode(WITHDRAW_SIG, IMX_TOKEN, address(this), address(this), withdrawAmount);
bytes memory data = abi.encode(WITHDRAW_SIG, token, address(this), address(this), withdrawAmount);

vm.prank(address(mockAxelarAdaptor));
vm.expectRevert(InvalidSourceChain.selector);
rootBridge.onMessageReceive("ding_dong", CHILD_BRIDGE_ADAPTOR_STRING, data);
}

function test_RevertsIf_OnMessageReceiveWithInvalidSourceAddress() public {
bytes memory data = abi.encode(WITHDRAW_SIG, IMX_TOKEN, address(this), address(this), withdrawAmount);
bytes memory data = abi.encode(WITHDRAW_SIG, token, address(this), address(this), withdrawAmount);

console2.log(CHILD_CHAIN_NAME);
console2.log(rootBridge.childChain());
Expand All @@ -89,14 +94,14 @@ contract RootERC20BridgeWithdrawUnitTest is Test, IRootERC20BridgeEvents, IRootE
}

function test_RevertsIf_OnMessageReceiveWithInvalidSignature() public {
bytes memory data = abi.encode(keccak256("RANDOM"), IMX_TOKEN, address(this), address(this), withdrawAmount);
bytes memory data = abi.encode(keccak256("RANDOM"), token, address(this), address(this), withdrawAmount);
vm.prank(address(mockAxelarAdaptor));
vm.expectRevert(InvalidData.selector);
rootBridge.onMessageReceive(CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, data);
}

function test_RevertsIf_OnMessageReceiveWithUnmappedToken() public {
bytes memory data = abi.encode(WITHDRAW_SIG, IMX_TOKEN, address(this), address(this), withdrawAmount);
bytes memory data = abi.encode(WITHDRAW_SIG, UnmappedToken, address(this), address(this), withdrawAmount);
vm.prank(address(mockAxelarAdaptor));
vm.expectRevert(NotMapped.selector);
rootBridge.onMessageReceive(CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, data);
Expand All @@ -121,6 +126,23 @@ contract RootERC20BridgeWithdrawUnitTest is Test, IRootERC20BridgeEvents, IRootE
);
}

function test_onMessageReceive_TransfersIMXTokens() public {
// Give bridge some IMX tokens
imxToken.transfer(address(rootBridge), 100 ether);

uint256 thisPreBal = imxToken.balanceOf(address(this));
uint256 bridgePreBal = imxToken.balanceOf(address(rootBridge));

bytes memory data = abi.encode(WITHDRAW_SIG, imxToken, address(this), address(this), withdrawAmount);
vm.prank(address(mockAxelarAdaptor));
rootBridge.onMessageReceive(CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, data);

assertEq(imxToken.balanceOf(address(this)), thisPreBal + withdrawAmount, "IMX not transferred to receiver");
assertEq(
imxToken.balanceOf(address(rootBridge)), bridgePreBal - withdrawAmount, "IMX not transferred from bridge"
);
}

function test_onMessageReceive_TransfersTokens_DifferentReceiver() public {
address receiver = address(123456);
// Need to first map the token.
Expand All @@ -141,6 +163,24 @@ contract RootERC20BridgeWithdrawUnitTest is Test, IRootERC20BridgeEvents, IRootE
);
}

function test_onMessageReceive_TransfersIMXTokens_DifferentReceiver() public {
address receiver = address(123456);
// Give bridge some IMX tokens
imxToken.transfer(address(rootBridge), 100 ether);

uint256 thisPreBal = imxToken.balanceOf(address(receiver));
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: same comment as above on thisPreBal

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you, updated.

uint256 bridgePreBal = imxToken.balanceOf(address(rootBridge));

bytes memory data = abi.encode(WITHDRAW_SIG, imxToken, address(this), receiver, withdrawAmount);
vm.prank(address(mockAxelarAdaptor));
rootBridge.onMessageReceive(CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, data);

assertEq(imxToken.balanceOf(address(receiver)), thisPreBal + withdrawAmount, "IMX not transferred to receiver");
assertEq(
imxToken.balanceOf(address(rootBridge)), bridgePreBal - withdrawAmount, "IMX not transferred from bridge"
);
}

function test_onMessageReceive_EmitsRootChainERC20WithdrawEvent() public {
// Need to first map the token.
rootBridge.mapToken(token);
Expand All @@ -160,6 +200,17 @@ contract RootERC20BridgeWithdrawUnitTest is Test, IRootERC20BridgeEvents, IRootE
rootBridge.onMessageReceive(CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, data);
}

function test_onMessageReceive_EmitsRootChainERC20WithdrawEventForIMX() public {
// Give bridge some IMX tokens
imxToken.transfer(address(rootBridge), 100 ether);

bytes memory data = abi.encode(WITHDRAW_SIG, imxToken, address(this), address(this), withdrawAmount);
vm.expectEmit();
emit RootChainERC20Withdraw(address(imxToken), NATIVE_IMX, address(this), address(this), withdrawAmount);
vm.prank(address(mockAxelarAdaptor));
rootBridge.onMessageReceive(CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, data);
}

function test_onMessageReceive_EmitsRootChainERC20WithdrawEvent_DifferentReceiver() public {
address receiver = address(123456);
// Need to first map the token.
Expand All @@ -175,4 +226,16 @@ contract RootERC20BridgeWithdrawUnitTest is Test, IRootERC20BridgeEvents, IRootE
vm.prank(address(mockAxelarAdaptor));
rootBridge.onMessageReceive(CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, data);
}

function test_onMessageReceive_EmitsRootChainERC20WithdrawEventForIMX_DifferentReceiver() public {
address receiver = address(123456);
// Give bridge some IMX tokens
imxToken.transfer(address(rootBridge), 100 ether);

bytes memory data = abi.encode(WITHDRAW_SIG, imxToken, address(this), receiver, withdrawAmount);
vm.expectEmit();
emit RootChainERC20Withdraw(address(imxToken), NATIVE_IMX, address(this), receiver, withdrawAmount);
vm.prank(address(mockAxelarAdaptor));
rootBridge.onMessageReceive(CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, data);
}
}