Skip to content

Commit

Permalink
All tests except integration
Browse files Browse the repository at this point in the history
  • Loading branch information
Benjimmutable committed Nov 2, 2023
1 parent b98a961 commit 8a01761
Show file tree
Hide file tree
Showing 14 changed files with 441 additions and 38 deletions.
2 changes: 1 addition & 1 deletion src/child/ChildAxelarBridgeAdaptor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ contract ChildAxelarBridgeAdaptor is
);

gateway.callContract(_rootChain, _rootBridgeAdaptor, payload);
emit MapTokenAxelarMessage(_rootChain, _rootBridgeAdaptor, payload);
emit AxelarMessage(_rootChain, _rootBridgeAdaptor, payload);
}

/**
Expand Down
8 changes: 5 additions & 3 deletions src/child/ChildERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,11 @@ contract ChildERC20Bridge is
}
}

function withdraw(IChildERC20 childToken, uint256 amount) external {
function withdraw(IChildERC20 childToken, uint256 amount) external payable {
_withdraw(childToken, msg.sender, amount);
}

function withdrawTo(IChildERC20 childToken, address receiver, uint256 amount) external {
function withdrawTo(IChildERC20 childToken, address receiver, uint256 amount) external payable {
_withdraw(childToken, receiver, amount);
}

Expand Down Expand Up @@ -159,13 +159,15 @@ contract ChildERC20Bridge is
revert BurnFailed();
}


// TODO Should we enforce receiver != 0? old poly contracts don't

// Encode the message payload
bytes memory payload = abi.encode(WITHDRAW_SIG, rootToken, msg.sender, receiver, amount);

// Send the message to the bridge adaptor and up to root chain
bridgeAdaptor.sendMessage{value: msg.value}(payload, msg.sender);

// TODO emit event
emit ChildChainERC20Withdraw(rootToken, address(childToken), msg.sender, receiver, amount);
}

Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/child/IChildAxelarBridgeAdaptor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ interface IChildAxelarBridgeAdaptorErrors {

interface IChildAxelarBridgeAdaptorEvents {
/// @notice Emitted when an Axelar message is sent to the root chain.
event MapTokenAxelarMessage(string indexed rootChain, string indexed rootBridgeAdaptor, bytes indexed payload);
event AxelarMessage(string indexed rootChain, string indexed rootBridgeAdaptor, bytes indexed payload);
}
2 changes: 1 addition & 1 deletion src/interfaces/root/IRootAxelarBridgeAdaptor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ interface IRootAxelarBridgeAdaptorErrors {

interface IRootAxelarBridgeAdaptorEvents {
/// @notice Emitted when an Axelar message is sent to the child chain.
event MapTokenAxelarMessage(string indexed childChain, string indexed childBridgeAdaptor, bytes indexed payload);
event AxelarMessage(string indexed childChain, string indexed childBridgeAdaptor, bytes indexed payload);
}
2 changes: 1 addition & 1 deletion src/root/RootAxelarBridgeAdaptor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,6 @@ contract RootAxelarBridgeAdaptor is
);

axelarGateway.callContract(_childChain, _childBridgeAdaptor, payload);
emit MapTokenAxelarMessage(_childChain, _childBridgeAdaptor, payload);
emit AxelarMessage(_childChain, _childBridgeAdaptor, payload);
}
}
18 changes: 18 additions & 0 deletions src/test/child/ChildERC20FailOnBurn.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: Apache 2.0
// Adapted from OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.21;

import "../../child/ChildERC20.sol";

/**
* @title ChildERC20FailOnBurn
* @author Immutable (@Benjimmutable)
* @notice ChildERC20 contract, except burn always returns false. Used for testing.
* @dev USED FOR TESTING
*/
// solhint-disable reason-string
contract ChildERC20FailOnBurn is ChildERC20 {
function burn(address account, uint256 amount) public virtual override returns (bool) {
return false;
}
}
2 changes: 2 additions & 0 deletions src/test/child/MockChildAxelarGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ contract MockChildAxelarGateway {
function validateContractCall(bytes32, string calldata, string calldata, bytes32) external pure returns (bool) {
return true;
}

function callContract(string memory childChain, string memory childBridgeAdaptor, bytes memory payload) external {}
}
Empty file.
Empty file.
12 changes: 6 additions & 6 deletions test/integration/root/RootERC20Bridge.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx

bytes memory payload = abi.encode(MAP_TOKEN_SIG, address(token), token.name(), token.symbol(), token.decimals());
vm.expectEmit(true, true, true, false, address(axelarAdaptor));
emit MapTokenAxelarMessage(CHILD_CHAIN_NAME, Strings.toHexString(CHILD_BRIDGE_ADAPTOR), payload);
emit AxelarMessage(CHILD_CHAIN_NAME, Strings.toHexString(CHILD_BRIDGE_ADAPTOR), payload);

vm.expectEmit(true, true, false, false, address(rootBridge));
emit L1TokenMapped(address(token), childToken);
Expand Down Expand Up @@ -112,7 +112,7 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx
console2.logBytes(predictedPayload);

vm.expectEmit(address(axelarAdaptor));
emit MapTokenAxelarMessage(CHILD_CHAIN_NAME, childBridgeAdaptorString, predictedPayload);
emit AxelarMessage(CHILD_CHAIN_NAME, childBridgeAdaptorString, predictedPayload);
vm.expectEmit(address(rootBridge));
emit NativeEthDeposit(
address(NATIVE_ETH), rootBridge.childETHToken(), address(this), address(this), tokenAmount
Expand Down Expand Up @@ -168,7 +168,7 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx
setupDeposit(IMX_TOKEN_ADDRESS, rootBridge, mapTokenFee, depositFee, tokenAmount, false);

vm.expectEmit(address(axelarAdaptor));
emit MapTokenAxelarMessage(CHILD_CHAIN_NAME, childBridgeAdaptorString, predictedPayload);
emit AxelarMessage(CHILD_CHAIN_NAME, childBridgeAdaptorString, predictedPayload);
vm.expectEmit(address(rootBridge));
emit IMXDeposit(address(IMX_TOKEN_ADDRESS), address(this), address(this), tokenAmount);

Expand Down Expand Up @@ -225,7 +225,7 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx
setupDeposit(WRAPPED_ETH, rootBridge, mapTokenFee, depositFee, tokenAmount, false);

vm.expectEmit(address(axelarAdaptor));
emit MapTokenAxelarMessage(CHILD_CHAIN_NAME, childBridgeAdaptorString, predictedPayload);
emit AxelarMessage(CHILD_CHAIN_NAME, childBridgeAdaptorString, predictedPayload);
vm.expectEmit(address(rootBridge));
emit WETHDeposit(address(WRAPPED_ETH), rootBridge.childETHToken(), address(this), address(this), tokenAmount);
vm.expectCall(
Expand Down Expand Up @@ -283,7 +283,7 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx
setupDeposit(address(token), rootBridge, mapTokenFee, depositFee, tokenAmount, true);

vm.expectEmit(address(axelarAdaptor));
emit MapTokenAxelarMessage(CHILD_CHAIN_NAME, childBridgeAdaptorString, predictedPayload);
emit AxelarMessage(CHILD_CHAIN_NAME, childBridgeAdaptorString, predictedPayload);
vm.expectEmit(address(rootBridge));
emit ChildChainERC20Deposit(address(token), childToken, address(this), address(this), tokenAmount);

Expand Down Expand Up @@ -339,7 +339,7 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx
setupDepositTo(address(token), rootBridge, mapTokenFee, depositFee, tokenAmount, recipient, true);

vm.expectEmit(address(axelarAdaptor));
emit MapTokenAxelarMessage(CHILD_CHAIN_NAME, childBridgeAdaptorString, predictedPayload);
emit AxelarMessage(CHILD_CHAIN_NAME, childBridgeAdaptorString, predictedPayload);
vm.expectEmit(address(rootBridge));
emit ChildChainERC20Deposit(address(token), childToken, address(this), recipient, tokenAmount);

Expand Down
134 changes: 126 additions & 8 deletions test/unit/child/ChildAxelarBridgeAdaptor.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,37 @@ pragma solidity ^0.8.21;
import {Test, console2} from "forge-std/Test.sol";
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {ERC20PresetMinterPauser} from "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol";
import {ChildAxelarBridgeAdaptor} from "../../../src/child/ChildAxelarBridgeAdaptor.sol";
import {MockChildERC20Bridge} from "../../../src/test/child/MockChildERC20Bridge.sol";
import {MockChildAxelarGateway} from "../../../src/test/child/MockChildAxelarGateway.sol";
import {MockChildAxelarGasService} from "../../../src/test/child/MockChildAxelarGasService.sol";
import {IChildAxelarBridgeAdaptorErrors} from "../../../src/interfaces/child/IChildAxelarBridgeAdaptor.sol";
import {IChildAxelarBridgeAdaptorErrors, IChildAxelarBridgeAdaptorEvents} from "../../../src/interfaces/child/IChildAxelarBridgeAdaptor.sol";

contract ChildAxelarBridgeAdaptorUnitTest is Test, IChildAxelarBridgeAdaptorErrors {
contract ChildAxelarBridgeAdaptorUnitTest is Test, IChildAxelarBridgeAdaptorErrors, IChildAxelarBridgeAdaptorEvents {
address public GATEWAY_ADDRESS = address(1);
string public constant ROOT_CHAIN_NAME = "root";
bytes32 public constant WITHDRAW_SIG = keccak256("WITHDRAW");

ChildAxelarBridgeAdaptor public childAxelarBridgeAdaptor;
ERC20PresetMinterPauser public token;
ChildAxelarBridgeAdaptor public axelarAdaptor;
MockChildERC20Bridge public mockChildERC20Bridge;
MockChildAxelarGateway public mockChildAxelarGateway;
MockChildAxelarGasService public mockChildAxelarGasService;

function setUp() public {
token = new ERC20PresetMinterPauser("Test", "TST");
mockChildERC20Bridge = new MockChildERC20Bridge();
mockChildAxelarGateway = new MockChildAxelarGateway();
mockChildAxelarGasService = new MockChildAxelarGasService();
childAxelarBridgeAdaptor = new ChildAxelarBridgeAdaptor(address(mockChildAxelarGateway));
childAxelarBridgeAdaptor.initialize("root", address(mockChildERC20Bridge), address(mockChildAxelarGasService));
axelarAdaptor = new ChildAxelarBridgeAdaptor(address(mockChildAxelarGateway));
axelarAdaptor.initialize(ROOT_CHAIN_NAME, address(mockChildERC20Bridge), address(mockChildAxelarGasService));
}

function test_Constructor_SetsValues() public {
assertEq(address(childAxelarBridgeAdaptor.childBridge()), address(mockChildERC20Bridge), "childBridge not set");
assertEq(address(childAxelarBridgeAdaptor.gateway()), address(mockChildAxelarGateway), "gateway not set");
assertEq(address(axelarAdaptor.childBridge()), address(mockChildERC20Bridge), "childBridge not set");
assertEq(address(axelarAdaptor.gateway()), address(mockChildAxelarGateway), "gateway not set");
assertEq(axelarAdaptor.rootChain(), ROOT_CHAIN_NAME, "rootChain not set");
}

// TODO add more initialize tests
Expand All @@ -49,6 +55,118 @@ contract ChildAxelarBridgeAdaptorUnitTest is Test, IChildAxelarBridgeAdaptorErro
address(mockChildERC20Bridge),
abi.encodeWithSelector(mockChildERC20Bridge.onMessageReceive.selector, sourceChain, sourceAddress, payload)
);
childAxelarBridgeAdaptor.execute(commandId, sourceChain, sourceAddress, payload);
axelarAdaptor.execute(commandId, sourceChain, sourceAddress, payload);
}

/// @dev For this unit test we just want to make sure the correct functions are called on the Axelar Gateway and Gas Service.
function test_sendMessage_CallsGasService() public {
address refundRecipient = address(123);
bytes memory payload = abi.encode(WITHDRAW_SIG, address(token), address(this), address(999), 11111);
uint256 callValue = 300;

vm.expectCall(
address(mockChildAxelarGasService),
callValue,
abi.encodeWithSelector(
mockChildAxelarGasService.payNativeGasForContractCall.selector,
address(axelarAdaptor),
ROOT_CHAIN_NAME,
mockChildERC20Bridge.rootERC20BridgeAdaptor(),
payload,
refundRecipient
)
);

vm.deal(address(mockChildERC20Bridge), callValue);
vm.prank(address(mockChildERC20Bridge));
axelarAdaptor.sendMessage{value: callValue}(payload, refundRecipient);
}

function test_sendMessage_CallsGateway() public {
bytes memory payload = abi.encode(WITHDRAW_SIG, address(token), address(this), address(999), 11111);
uint256 callValue = 300;

vm.expectCall(
address(mockChildAxelarGateway),
abi.encodeWithSelector(
mockChildAxelarGateway.callContract.selector, ROOT_CHAIN_NAME, mockChildERC20Bridge.rootERC20BridgeAdaptor(), payload
)
);

vm.deal(address(mockChildERC20Bridge), callValue);
vm.prank(address(mockChildERC20Bridge));
axelarAdaptor.sendMessage{value: callValue}(payload, address(123));
}

function test_sendMessage_EmitsAxelarMessageEvent() public {
bytes memory payload = abi.encode(WITHDRAW_SIG, address(token), address(this), address(999), 11111);
uint256 callValue = 300;

vm.expectEmit();
emit AxelarMessage(ROOT_CHAIN_NAME, mockChildERC20Bridge.rootERC20BridgeAdaptor(), payload);

vm.deal(address(mockChildERC20Bridge), callValue);
vm.prank(address(mockChildERC20Bridge));
axelarAdaptor.sendMessage{value: callValue}(payload, address(123));
}

function testFuzz_sendMessage_PaysGasToGasService(uint256 callValue) public {
vm.assume(callValue < address(this).balance);
vm.assume(callValue > 0);
vm.deal(address(mockChildERC20Bridge), callValue);

bytes memory payload = abi.encode(WITHDRAW_SIG, address(token), address(this), address(999), 11111);

uint256 bridgePreBal = address(mockChildERC20Bridge).balance;
uint256 axelarGasServicePreBal = address(mockChildAxelarGasService).balance;

vm.prank(address(mockChildERC20Bridge));
axelarAdaptor.sendMessage{value: callValue}(payload, address(123));

assertEq(address(mockChildERC20Bridge).balance, bridgePreBal - callValue, "ETH balance not decreased");
assertEq(address(mockChildAxelarGasService).balance, axelarGasServicePreBal + callValue, "ETH not paid to gas service");
}

function test_sendMessage_GivesCorrectRefundRecipient() public {
address refundRecipient = address(0x3333);
uint256 callValue = 300;

bytes memory payload = abi.encode(WITHDRAW_SIG, address(token), address(this), address(999), 11111);

vm.expectCall(
address(mockChildAxelarGasService),
callValue,
abi.encodeWithSelector(
mockChildAxelarGasService.payNativeGasForContractCall.selector,
address(axelarAdaptor),
ROOT_CHAIN_NAME,
mockChildERC20Bridge.rootERC20BridgeAdaptor(),
payload,
refundRecipient
)
);

vm.deal(address(mockChildERC20Bridge), callValue);
vm.prank(address(mockChildERC20Bridge));
axelarAdaptor.sendMessage{value: callValue}(payload, refundRecipient);
}

function test_RevertIf_mapTokenCalledByNonRootBridge() public {
address payable prankster = payable(address(0x33));
uint256 value = 300;
bytes memory payload = abi.encode(WITHDRAW_SIG, address(token), address(this), address(999), 11111);

// Have to call these above so the expectRevert works on the call to mapToken.
prankster.transfer(value);
vm.prank(prankster);
vm.expectRevert(CallerNotBridge.selector);
axelarAdaptor.sendMessage{value: value}(payload, address(123));
}

function test_RevertIf_mapTokenCalledWithNoValue() public {
bytes memory payload = abi.encode(WITHDRAW_SIG, address(token), address(this), address(999), 11111);
vm.expectRevert(NoGas.selector);
vm.prank(address(mockChildERC20Bridge));
axelarAdaptor.sendMessage{value: 0}(payload, address(123));
}
}
Loading

0 comments on commit 8a01761

Please sign in to comment.