-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1bf7911
commit e3efc7d
Showing
3 changed files
with
312 additions
and
0 deletions.
There are no files selected for viewing
91 changes: 91 additions & 0 deletions
91
test/integration/root/withdrawals.t.sol/RootERC20BridgeWithdraw.t.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
// SPDX-License-Identifier: Apache 2.0 | ||
pragma solidity ^0.8.21; | ||
|
||
import {Test, console2} from "forge-std/Test.sol"; | ||
import {ERC20PresetMinterPauser} from "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; | ||
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; | ||
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; | ||
import {MockAxelarGateway} from "../../../../src/test/root/MockAxelarGateway.sol"; | ||
import {MockAxelarGasService} from "../../../../src/test/root/MockAxelarGasService.sol"; | ||
import {RootERC20Bridge, IRootERC20BridgeEvents, IERC20Metadata} from "../../../../src/root/RootERC20Bridge.sol"; | ||
import {RootAxelarBridgeAdaptor, IRootAxelarBridgeAdaptorEvents} from "../../../../src/root/RootAxelarBridgeAdaptor.sol"; | ||
import {Utils} from "../../../utils.t.sol"; | ||
import {WETH} from "../../../../src/test/root/WETH.sol"; | ||
|
||
contract RootERC20BridgeWithdrawIntegrationTest is Test, IRootERC20BridgeEvents, IRootAxelarBridgeAdaptorEvents, Utils { | ||
address constant CHILD_BRIDGE = address(3); | ||
address constant CHILD_BRIDGE_ADAPTOR = address(4); | ||
string constant CHILD_CHAIN_NAME = "CHILD"; | ||
address constant IMX_TOKEN_ADDRESS = address(0xccc); | ||
address constant NATIVE_ETH = address(0xeee); | ||
address constant WRAPPED_ETH = address(0xddd); | ||
|
||
uint256 constant withdrawAmount = 0.5 ether; | ||
|
||
ERC20PresetMinterPauser public token; | ||
ERC20PresetMinterPauser public imxToken; | ||
RootERC20Bridge public rootBridge; | ||
RootAxelarBridgeAdaptor public axelarAdaptor; | ||
MockAxelarGateway public mockAxelarGateway; | ||
MockAxelarGasService public axelarGasService; | ||
|
||
function setUp() public { | ||
deployCodeTo("WETH.sol", abi.encode("Wrapped ETH", "WETH"), WRAPPED_ETH); | ||
|
||
(imxToken, token, rootBridge, axelarAdaptor, mockAxelarGateway, axelarGasService) = | ||
rootIntegrationSetup(CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, CHILD_CHAIN_NAME, IMX_TOKEN_ADDRESS, WRAPPED_ETH); | ||
|
||
// Need to first map the token. | ||
rootBridge.mapToken{value:1}(token); | ||
// And give the bridge some tokens | ||
token.transfer(address(rootBridge), 100 ether); | ||
} | ||
|
||
function test_withdraw_TransfersTokens() public { | ||
bytes memory data = abi.encode(WITHDRAW_SIG, address(token), address(this), address(this), withdrawAmount); | ||
|
||
bytes32 commandId = bytes32("testCommandId"); | ||
string memory sourceAddress = rootBridge.childBridgeAdaptor(); | ||
|
||
uint256 thisPreBal = token.balanceOf(address(this)); | ||
uint256 bridgePreBal = token.balanceOf(address(rootBridge)); | ||
|
||
axelarAdaptor.execute(commandId, CHILD_CHAIN_NAME, sourceAddress, data); | ||
|
||
uint256 thisPostBal = token.balanceOf(address(this)); | ||
uint256 bridgePostBal = token.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); | ||
|
||
bytes32 commandId = bytes32("testCommandId"); | ||
string memory sourceAddress = rootBridge.childBridgeAdaptor(); | ||
|
||
uint256 receiverPreBal = token.balanceOf(receiver); | ||
uint256 bridgePreBal = token.balanceOf(address(rootBridge)); | ||
|
||
axelarAdaptor.execute(commandId, CHILD_CHAIN_NAME, sourceAddress, data); | ||
|
||
uint256 receiverPostBal = token.balanceOf(receiver); | ||
uint256 bridgePostBal = token.balanceOf(address(rootBridge)); | ||
|
||
assertEq(receiverPostBal, receiverPreBal + 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); | ||
|
||
bytes32 commandId = bytes32("testCommandId"); | ||
string memory sourceAddress = rootBridge.childBridgeAdaptor(); | ||
|
||
vm.expectEmit(); | ||
emit RootChainERC20Withdraw(address(token), rootBridge.rootTokenToChildToken(address(token)), address(this), address(this), withdrawAmount); | ||
axelarAdaptor.execute(commandId, CHILD_CHAIN_NAME, sourceAddress, data); | ||
} | ||
} |
54 changes: 54 additions & 0 deletions
54
test/unit/root/withdrawals/RootAxelarBridgeAdaptorWithdraw.t.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// SPDX-License-Identifier: Apache 2.0 | ||
pragma solidity ^0.8.21; | ||
|
||
import {Test, console2} from "forge-std/Test.sol"; | ||
import {ERC20PresetMinterPauser} from "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; | ||
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; | ||
import {MockAxelarGateway} from "../../../../src/test/root/MockAxelarGateway.sol"; | ||
import {MockAxelarGasService} from "../../../../src/test/root/MockAxelarGasService.sol"; | ||
import { | ||
RootAxelarBridgeAdaptor, | ||
IRootAxelarBridgeAdaptorEvents, | ||
IRootAxelarBridgeAdaptorErrors | ||
} from "../../../../src/root/RootAxelarBridgeAdaptor.sol"; | ||
import {StubRootBridge} from "../../../../src/test/root/StubRootBridge.sol"; | ||
|
||
contract RootAxelarBridgeWithdrawAdaptorTest is Test, IRootAxelarBridgeAdaptorEvents, IRootAxelarBridgeAdaptorErrors { | ||
address constant CHILD_BRIDGE = address(3); | ||
string public childBridgeAdaptor; | ||
string constant CHILD_CHAIN_NAME = "test"; | ||
bytes32 public constant MAP_TOKEN_SIG = keccak256("MAP_TOKEN"); | ||
|
||
ERC20PresetMinterPauser public token; | ||
RootAxelarBridgeAdaptor public axelarAdaptor; | ||
MockAxelarGateway public mockAxelarGateway; | ||
MockAxelarGasService public axelarGasService; | ||
StubRootBridge public stubRootBridge; | ||
|
||
function setUp() public { | ||
token = new ERC20PresetMinterPauser("Test", "TST"); | ||
mockAxelarGateway = new MockAxelarGateway(); | ||
axelarGasService = new MockAxelarGasService(); | ||
stubRootBridge = new StubRootBridge(); | ||
childBridgeAdaptor = stubRootBridge.childBridgeAdaptor(); | ||
|
||
axelarAdaptor = new RootAxelarBridgeAdaptor(address(mockAxelarGateway)); | ||
axelarAdaptor.initialize(address(stubRootBridge), CHILD_CHAIN_NAME, address(axelarGasService)); | ||
vm.deal(address(stubRootBridge), 99999999999); | ||
} | ||
|
||
|
||
function test_execute_callsBridge() public { | ||
bytes32 commandId = bytes32("testCommandId"); | ||
string memory sourceChain = "test"; | ||
string memory sourceAddress = Strings.toHexString(address(123)); | ||
bytes memory payload = abi.encodePacked("payload"); | ||
|
||
// We expect to call the bridge's onMessageReceive function. | ||
vm.expectCall( | ||
address(stubRootBridge), | ||
abi.encodeWithSelector(stubRootBridge.onMessageReceive.selector, sourceChain, sourceAddress, payload) | ||
); | ||
axelarAdaptor.execute(commandId, sourceChain, sourceAddress, payload); | ||
} | ||
} |
167 changes: 167 additions & 0 deletions
167
test/unit/root/withdrawals/RootERC20BridgeWithdraw.t.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
// SPDX-License-Identifier: Apache 2.0 | ||
pragma solidity ^0.8.21; | ||
|
||
import {Test, console2} from "forge-std/Test.sol"; | ||
import {ERC20PresetMinterPauser} from "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; | ||
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; | ||
import { | ||
RootERC20Bridge, | ||
IRootERC20BridgeEvents, | ||
IRootERC20BridgeErrors | ||
} from "../../../../src/root/RootERC20Bridge.sol"; | ||
import {MockAxelarGateway} from "../../../../src/test/root/MockAxelarGateway.sol"; | ||
import {MockAxelarGasService} from "../../../../src/test/root/MockAxelarGasService.sol"; | ||
import {MockAdaptor} from "../../../../src/test/root/MockAdaptor.sol"; | ||
import {Utils} from "../../../utils.t.sol"; | ||
|
||
contract RootERC20BridgeWithdrawUnitTest is Test, IRootERC20BridgeEvents, IRootERC20BridgeErrors, Utils { | ||
address constant CHILD_BRIDGE = address(3); | ||
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 IMX_TOKEN = address(0xccc); | ||
address constant WRAPPED_ETH = address(0xddd); | ||
uint256 constant mapTokenFee = 300; | ||
uint256 constant withdrawAmount = 0.5 ether; | ||
|
||
ERC20PresetMinterPauser public token; | ||
RootERC20Bridge public rootBridge; | ||
MockAdaptor public mockAxelarAdaptor; | ||
MockAxelarGateway public mockAxelarGateway; | ||
MockAxelarGasService public axelarGasService; | ||
|
||
function setUp() public { | ||
token = new ERC20PresetMinterPauser("Test", "TST"); | ||
token.mint(address(this), 100 ether); | ||
deployCodeTo("ERC20PresetMinterPauser.sol", abi.encode("ImmutableX", "IMX"), IMX_TOKEN); | ||
|
||
deployCodeTo("WETH.sol", abi.encode("Wrapped ETH", "WETH"), WRAPPED_ETH); | ||
|
||
rootBridge = new RootERC20Bridge(); | ||
mockAxelarGateway = new MockAxelarGateway(); | ||
axelarGasService = new MockAxelarGasService(); | ||
|
||
mockAxelarAdaptor = new MockAdaptor(); | ||
|
||
// The specific ERC20 token template does not matter for these unit tests | ||
rootBridge.initialize( | ||
address(mockAxelarAdaptor), | ||
CHILD_BRIDGE, | ||
CHILD_BRIDGE_ADAPTOR_STRING, | ||
address(token), | ||
IMX_TOKEN, | ||
WRAPPED_ETH, | ||
CHILD_CHAIN_NAME | ||
); | ||
} | ||
|
||
function test_RevertsIf_WithdrawWithInvalidSender() public { | ||
bytes memory data = abi.encode(WITHDRAW_SIG, IMX_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); | ||
|
||
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); | ||
|
||
console2.log(CHILD_CHAIN_NAME); | ||
console2.log(rootBridge.childChain()); | ||
vm.prank(address(mockAxelarAdaptor)); | ||
vm.expectRevert(InvalidSourceAddress.selector); | ||
rootBridge.onMessageReceive(CHILD_CHAIN_NAME, "DING_DONG", data); | ||
} | ||
|
||
function test_RevertsIf_OnMessageReceiveWithZeroDataLength() public { | ||
bytes memory data; | ||
|
||
vm.prank(address(mockAxelarAdaptor)); | ||
vm.expectRevert(InvalidData.selector); | ||
rootBridge.onMessageReceive(CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, data); | ||
} | ||
|
||
function test_RevertsIf_OnMessageReceiveWithInvalidSignature() public { | ||
bytes memory data = abi.encode(keccak256("RANDOM"), IMX_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); | ||
vm.prank(address(mockAxelarAdaptor)); | ||
vm.expectRevert(NotMapped.selector); | ||
rootBridge.onMessageReceive(CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, data); | ||
} | ||
|
||
function test_onMessageReceive_TransfersTokens() public { | ||
// Need to first map the token. | ||
rootBridge.mapToken(token); | ||
// And give the bridge some tokens | ||
token.transfer(address(rootBridge), 100 ether); | ||
|
||
uint256 thisPreBal = token.balanceOf(address(this)); | ||
uint256 bridgePreBal = token.balanceOf(address(rootBridge)); | ||
|
||
bytes memory data = abi.encode(WITHDRAW_SIG, token, address(this), address(this), withdrawAmount); | ||
vm.prank(address(mockAxelarAdaptor)); | ||
rootBridge.onMessageReceive(CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, data); | ||
|
||
assertEq(token.balanceOf(address(this)), thisPreBal + withdrawAmount, "Tokens not transferred to receiver"); | ||
assertEq(token.balanceOf(address(rootBridge)), bridgePreBal - withdrawAmount, "Tokens not transferred from bridge"); | ||
} | ||
|
||
function test_onMessageReceive_TransfersTokens_DifferentReceiver() public { | ||
address receiver = address(123456); | ||
// Need to first map the token. | ||
rootBridge.mapToken(token); | ||
// And give the bridge some tokens | ||
token.transfer(address(rootBridge), 100 ether); | ||
|
||
uint256 receiverPreBal = token.balanceOf(receiver); | ||
uint256 bridgePreBal = token.balanceOf(address(rootBridge)); | ||
|
||
bytes memory data = abi.encode(WITHDRAW_SIG, token, address(this), receiver, withdrawAmount); | ||
vm.prank(address(mockAxelarAdaptor)); | ||
rootBridge.onMessageReceive(CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, data); | ||
|
||
assertEq(token.balanceOf(receiver), receiverPreBal + withdrawAmount, "Tokens not transferred to receiver"); | ||
assertEq(token.balanceOf(address(rootBridge)), bridgePreBal - withdrawAmount, "Tokens not transferred from bridge"); | ||
} | ||
|
||
function test_onMessageReceive_EmitsRootChainERC20WithdrawEvent() public { | ||
// Need to first map the token. | ||
rootBridge.mapToken(token); | ||
// And give the bridge some tokens | ||
token.transfer(address(rootBridge), 100 ether); | ||
|
||
bytes memory data = abi.encode(WITHDRAW_SIG, token, address(this), address(this), withdrawAmount); | ||
vm.expectEmit(); | ||
emit RootChainERC20Withdraw(address(token), rootBridge.rootTokenToChildToken(address(token)), 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. | ||
rootBridge.mapToken(token); | ||
// And give the bridge some tokens | ||
token.transfer(address(rootBridge), 100 ether); | ||
|
||
bytes memory data = abi.encode(WITHDRAW_SIG, token, address(this), receiver, withdrawAmount); | ||
vm.expectEmit(); | ||
emit RootChainERC20Withdraw(address(token), rootBridge.rootTokenToChildToken(address(token)), address(this), receiver, withdrawAmount); | ||
vm.prank(address(mockAxelarAdaptor)); | ||
rootBridge.onMessageReceive(CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, data); | ||
} | ||
|
||
} |