Skip to content

Commit

Permalink
integrate feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
Benjimmutable committed Sep 29, 2023
1 parent 89698a6 commit 19610b6
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 50 deletions.
16 changes: 4 additions & 12 deletions contracts/bridge/RootAxelarBridgeAdaptor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ contract RootAxelarBridgeAdaptor is IRootERC20BridgeAdaptor, IAxelarBridgeAdapto
string public childChain;
IAxelarGateway public immutable AXELAR_GATEWAY;
IAxelarGasService public immutable GAS_SERVICE;

bytes32 public constant MAP_TOKEN_SIG = keccak256("MAP_TOKEN");
mapping(uint256 => string) public chainIdToChainName;

constructor(
address _rootBridge,
Expand Down Expand Up @@ -56,23 +55,16 @@ contract RootAxelarBridgeAdaptor is IRootERC20BridgeAdaptor, IAxelarBridgeAdapto

/**
* @inheritdoc IRootERC20BridgeAdaptor
* @dev Send a map token message to the child chain via the Axelar Gateway.
* @notice Sends an arbitrary message to the child chain, via the Axelar network.
*/
function mapToken(
address rootToken,
string calldata name,
string calldata symbol,
uint8 decimals
) external payable override {
function sendMessage(bytes calldata payload, address refundRecipient) external payable override {
if (msg.value == 0) {
revert NoGas();
}
if (msg.sender != ROOT_BRIDGE) {
revert CallerNotBridge();
}

bytes memory payload = abi.encode(MAP_TOKEN_SIG, rootToken, name, symbol, decimals);

// Load from storage.
string memory _childBridgeAdaptor = childBridgeAdaptor;
string memory _childChain = childChain;
Expand All @@ -83,7 +75,7 @@ contract RootAxelarBridgeAdaptor is IRootERC20BridgeAdaptor, IAxelarBridgeAdapto
_childChain,
_childBridgeAdaptor,
payload,
msg.sender
refundRecipient
);

AXELAR_GATEWAY.callContract(_childChain, _childBridgeAdaptor, payload);
Expand Down
7 changes: 5 additions & 2 deletions contracts/bridge/RootERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ contract RootERC20Bridge is
IRootERC20BridgeErrors
{
using SafeERC20 for IERC20Metadata;
bytes32 public constant MAP_TOKEN_SIG = keccak256("MAP_TOKEN");

IRootERC20BridgeAdaptor public bridgeAdaptor;
/// @dev The address that will be minting tokens on the child chain.
Expand Down Expand Up @@ -82,12 +83,14 @@ contract RootERC20Bridge is

rootTokenToChildToken[address(rootToken)] = childToken;

bridgeAdaptor.mapToken{value: msg.value}(
address(rootToken),
bytes memory payload = abi.encode(
MAP_TOKEN_SIG,
rootToken,
rootToken.name(),
rootToken.symbol(),
rootToken.decimals()
);
bridgeAdaptor.sendMessage{value: msg.value}(payload, msg.sender);

emit TokenMapped(address(rootToken), childToken);
return childToken;
Expand Down
1 change: 1 addition & 0 deletions contracts/bridge/interfaces/IAxelarBridgeAdaptor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ interface IAxelarBridgeAdaptorErrors {
error InvalidChildChain();
error NoGas();
error CallerNotBridge();
error InvalidArrayLengths();
}

interface IAxelarBridgeAdaptorEvents {
Expand Down
12 changes: 6 additions & 6 deletions contracts/bridge/interfaces/IRootERC20BridgeAdaptor.sol
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

// TODO: This is likely able to become a generic bridge adaptor, not just for ERC20 tokens.
interface IRootERC20BridgeAdaptor {
/**
* @notice Send a map token message to the child chain via the message passing protocol.
* @param rootToken The address of the token on the root chain.
* @param name The name of the token.
* @param symbol The symbol of the token.
* @param decimals The decimals of the token.
* @notice Send an arbitrary message to the child chain via the message passing protocol.
* @param payload The message to send, encoded in a `bytes` array.
* @param refundRecipient Used if the message passing protocol requires fees & pays back excess to a refund recipient.
* @dev `payable` because the message passing protocol may require a fee to be paid.
*/
function mapToken(address rootToken, string calldata name, string calldata symbol, uint8 decimals) external payable;
function sendMessage(bytes calldata payload, address refundRecipient) external payable;
}
7 changes: 7 additions & 0 deletions contracts/bridge/test/MockAdaptor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

// @dev A contract for ensuring the Axelar Bridge Adaptor is called correctly during unit tests.
contract MockAdaptor {
function sendMessage(bytes calldata , address) external payable{}
}
12 changes: 0 additions & 12 deletions contracts/bridge/test/MockRootAxelarBridgeAdaptor.sol

This file was deleted.

12 changes: 10 additions & 2 deletions test/bridge/integration/RootERC20Bridge.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IAxelar
address(axelarAdaptor),
mapTokenFee,
abi.encodeWithSelector(
axelarAdaptor.mapToken.selector, address(token), token.name(), token.symbol(), token.decimals()
axelarAdaptor.sendMessage.selector, payload, address(this)
)
);

Expand All @@ -65,7 +65,7 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IAxelar
CHILD_CHAIN_NAME,
Strings.toHexString(CHILD_BRIDGE_ADAPTOR),
payload,
address(rootBridge)
address(this)
)
);

Expand All @@ -80,8 +80,16 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IAxelar
)
);

// Check that we pay mapTokenFee to the axelarGasService.
uint256 thisPreBal = address(this).balance;
uint256 axelarGasServicePreBal = address(axelarGasService).balance;

rootBridge.mapToken{value: mapTokenFee}(token);

// Should update ETH balances as gas payment for message.
assertEq(address(this).balance, thisPreBal - mapTokenFee);
assertEq(address(axelarGasService).balance, axelarGasServicePreBal + mapTokenFee);

assertEq(rootBridge.rootTokenToChildToken(address(token)), childToken);
}
}
70 changes: 59 additions & 11 deletions test/bridge/unit/RootAxelarBridgeAdaptor.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ contract RootAxelarBridgeAdaptorTest is Test, IAxelarBridgeAdaptorEvents, IAxela
}

/// @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_mapToken() public {
function test_sendMessage_CallsGasService() public {
address refundRecipient = address(123);
bytes memory payload = abi.encode(MAP_TOKEN_SIG, address(token), token.name(), token.symbol(), token.decimals());
uint256 callValue = 300;

Expand All @@ -78,10 +79,17 @@ contract RootAxelarBridgeAdaptorTest is Test, IAxelarBridgeAdaptorEvents, IAxela
CHILD_CHAIN_NAME,
Strings.toHexString(CHILD_BRIDGE_ADAPTOR),
payload,
address(this)
refundRecipient
)
);

axelarAdaptor.sendMessage{value: callValue}(payload, refundRecipient);
}

function test_sendMessage_CallsGateway() public {
bytes memory payload = abi.encode(MAP_TOKEN_SIG, address(token), token.name(), token.symbol(), token.decimals());
uint256 callValue = 300;

vm.expectCall(
address(mockAxelarGateway),
abi.encodeWithSelector(
Expand All @@ -92,31 +100,71 @@ contract RootAxelarBridgeAdaptorTest is Test, IAxelarBridgeAdaptorEvents, IAxela
)
);

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

function test_sendMessage_EmitsMapTokenAxelarMessageEvent() public {
bytes memory payload = abi.encode(MAP_TOKEN_SIG, address(token), token.name(), token.symbol(), token.decimals());
uint256 callValue = 300;

vm.expectEmit(true, true, true, false, address(axelarAdaptor));
emit MapTokenAxelarMessage(CHILD_CHAIN_NAME, Strings.toHexString(CHILD_BRIDGE_ADAPTOR), payload);

axelarAdaptor.mapToken{value: callValue}(address(token), token.name(), token.symbol(), token.decimals());
axelarAdaptor.sendMessage{value: callValue}(payload, address(123));
}

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

bytes memory payload = abi.encode(MAP_TOKEN_SIG, address(token), token.name(), token.symbol(), token.decimals());

uint256 thisPreBal = address(this).balance;
uint256 axelarGasServicePreBal = address(axelarGasService).balance;

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

assertEq(address(this).balance, thisPreBal - callValue);
assertEq(address(axelarGasService).balance, axelarGasServicePreBal + callValue);
}

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

bytes memory payload = abi.encode(MAP_TOKEN_SIG, address(token), token.name(), token.symbol(), token.decimals());

vm.expectCall(
address(axelarGasService),
callValue,
abi.encodeWithSelector(
axelarGasService.payNativeGasForContractCall.selector,
address(axelarAdaptor),
CHILD_CHAIN_NAME,
Strings.toHexString(CHILD_BRIDGE_ADAPTOR),
payload,
refundRecipient
)
);

axelarAdaptor.sendMessage{value: callValue}(payload, refundRecipient);
}

function test_RevertsIf_mapTokenCalledByNonRootBridge() public {
address payable prankster = payable(address(0x33));
uint256 value = 300;
bytes memory payload = abi.encode(MAP_TOKEN_SIG, address(token), token.name(), token.symbol(), token.decimals());

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

function test_RevertsIf_mapTokenCalledWithNoValue() public {
string memory name = token.name();
string memory symbol = token.symbol();
uint8 decimals = token.decimals();
bytes memory payload = abi.encode(MAP_TOKEN_SIG, address(token), token.name(), token.symbol(), token.decimals());
vm.expectRevert(NoGas.selector);
axelarAdaptor.mapToken{value: 0}(address(token), name, symbol, decimals);
axelarAdaptor.sendMessage{value: 0}(payload, address(123));
}
}
48 changes: 43 additions & 5 deletions test/bridge/unit/RootERC20Bridge.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {MockAxelarGateway} from "../../../contracts/bridge/test/MockAxelarGateway.sol";
import {MockAxelarGasService} from "../../../contracts/bridge/test/MockAxelarGasService.sol";
import {RootERC20Bridge, IRootERC20BridgeEvents, IERC20Metadata, IRootERC20BridgeErrors } from "../../../contracts/bridge/RootERC20Bridge.sol";
import {MockRootAxelarBridgeAdaptor} from "../../../contracts/bridge/test/MockRootAxelarBridgeAdaptor.sol";
import {MockAdaptor} from "../../../contracts/bridge/test/MockAdaptor.sol";

contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20BridgeErrors {
address constant CHILD_BRIDGE = address(3);
Expand All @@ -17,7 +17,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid

ERC20PresetMinterPauser public token;
RootERC20Bridge public rootBridge;
MockRootAxelarBridgeAdaptor public mockAxelarAdaptor;
MockAdaptor public mockAxelarAdaptor;
MockAxelarGateway public mockAxelarGateway;
MockAxelarGasService public axelarGasService;

Expand All @@ -28,7 +28,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid
mockAxelarGateway = new MockAxelarGateway();
axelarGasService = new MockAxelarGasService();

mockAxelarAdaptor = new MockRootAxelarBridgeAdaptor();
mockAxelarAdaptor = new MockAdaptor();

// The specific ERC20 token template does not matter for these unit tests
rootBridge.initialize(address(mockAxelarAdaptor), CHILD_BRIDGE, address(token));
Expand All @@ -51,26 +51,64 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid
bridge.initialize(address(0), address(0), address(0));
}

function test_mapToken() public {
function test_mapToken_EmitsTokenMappedEvent() public {
uint256 mapTokenFee = 300;
address childToken =
Clones.predictDeterministicAddress(address(token), keccak256(abi.encodePacked(token)), CHILD_BRIDGE);

vm.expectEmit(true, true, false, false, address(rootBridge));
emit TokenMapped(address(token), childToken);

rootBridge.mapToken{value: mapTokenFee}(token);

}

function test_mapToken_CallsAdaptor() public {
uint256 mapTokenFee = 300;

bytes memory payload = abi.encode(rootBridge.MAP_TOKEN_SIG(), token, token.name(), token.symbol(), token.decimals());

vm.expectCall(
address(mockAxelarAdaptor),
mapTokenFee,
abi.encodeWithSelector(
mockAxelarAdaptor.mapToken.selector, address(token), token.name(), token.symbol(), token.decimals()
mockAxelarAdaptor.sendMessage.selector, payload, address(this)
)
);

rootBridge.mapToken{value: mapTokenFee}(token);
}

function test_mapToken_SetsTokenMapping() public {
uint256 mapTokenFee = 300;
address childToken =
Clones.predictDeterministicAddress(address(token), keccak256(abi.encodePacked(token)), CHILD_BRIDGE);

rootBridge.mapToken{value: mapTokenFee}(token);

assertEq(rootBridge.rootTokenToChildToken(address(token)), childToken);
}

function testFuzz_mapToken_UpdatesEthBalance(uint256 mapTokenFee) public {
vm.assume(mapTokenFee < address(this).balance);
vm.assume(mapTokenFee > 0);
uint256 thisPreBal = address(this).balance;
uint256 rootBridgePreBal = address(rootBridge).balance;
uint256 adaptorPreBal = address(mockAxelarAdaptor).balance;

rootBridge.mapToken{value: mapTokenFee}(token);

/*
* Because this is a unit test, the adaptor is mocked. This adaptor would typically
* pay the ETH to the gas service, but in this mocked case it will keep the ETH.
*/

// User pays
assertEq(address(this).balance, thisPreBal - mapTokenFee);
assertEq(address(mockAxelarAdaptor).balance, adaptorPreBal + mapTokenFee);
assertEq(address(rootBridge).balance, rootBridgePreBal);
}

function test_RevertsIf_mapTokenCalledWithZeroAddress() public {
vm.expectRevert(ZeroAddress.selector);
rootBridge.mapToken{value: 300}(IERC20Metadata(address(0)));
Expand Down

0 comments on commit 19610b6

Please sign in to comment.