Warp route changes for v3 (#2721)

- V3 compatible ERC20, ERC721 and all their extensions

- Painstakingly migrating hardhat tests to foundry tests for all
variants

- fixes https://github.com/hyperlane-xyz/issues/issues/577

Yes

whole lotta unit tests and poor man's version of integration tests

---------

Signed-off-by: -f <kunalarora1729@gmail.com>
Co-authored-by: Yorke Rhodes <yorke@hyperlane.xyz>
pull/2736/head
Kunal Arora 1 year ago committed by Yorke Rhodes
parent 7309f770ef
commit f4b12a438b
No known key found for this signature in database
GPG Key ID: 9EEACF1DA75C5627
  1. 4
      README.md
  2. 18
      typescript/token/contracts/HypERC20.sol
  3. 18
      typescript/token/contracts/HypERC20Collateral.sol
  4. 19
      typescript/token/contracts/HypERC721.sol
  5. 22
      typescript/token/contracts/HypERC721Collateral.sol
  6. 18
      typescript/token/contracts/HypNative.sol
  7. 9
      typescript/token/contracts/extensions/HypERC721URIStorage.sol
  8. 7
      typescript/token/contracts/libs/TokenRouter.sol
  9. 6
      typescript/token/package.json
  10. 8
      typescript/token/src/deploy.ts
  11. 369
      typescript/token/test/HypERC20.t.sol
  12. 369
      typescript/token/test/HypERC721.t.sol
  13. 294
      typescript/token/test/erc20.test.ts
  14. 323
      typescript/token/test/erc721.test.ts

@ -12,7 +12,9 @@
## Versioning
Note this is the branch for Hyperlane v2.
Note this is the branch for Hyperlane v3.
V2 is on the main branch but is eventually being phased out.
V1 has since been deprecated in favor of V2, but if you are looking for code relating to the existing V1 deployments of the `testnet2` or `mainnet` environments, refer to the [v1](https://github.com/hyperlane-xyz/hyperlane-monorepo/tree/v1) branch.

@ -20,23 +20,18 @@ contract HypERC20 is ERC20Upgradeable, TokenRouter {
/**
* @notice Initializes the Hyperlane router, ERC20 metadata, and mints initial supply to deployer.
* @param _mailbox The address of the mailbox contract.
* @param _interchainGasPaymaster The address of the interchain gas paymaster contract.
* @param _totalSupply The initial supply of the token.
* @param _name The name of the token.
* @param _symbol The symbol of the token.
*/
function initialize(
address _mailbox,
address _interchainGasPaymaster,
uint256 _totalSupply,
string memory _name,
string memory _symbol
) external initializer {
// transfers ownership to `msg.sender`
__HyperlaneConnectionClient_initialize(
_mailbox,
_interchainGasPaymaster
);
// initialize router
__Router_initialize(_mailbox);
// Initialize ERC20 metadata
__ERC20_init(_name, _symbol);
@ -47,6 +42,15 @@ contract HypERC20 is ERC20Upgradeable, TokenRouter {
return _decimals;
}
function balanceOf(address _account)
public
view
override(TokenRouter, ERC20Upgradeable)
returns (uint256)
{
return ERC20Upgradeable.balanceOf(_account);
}
/**
* @dev Burns `_amount` of token from `msg.sender` balance.
* @inheritdoc TokenRouter

@ -27,19 +27,17 @@ contract HypERC20Collateral is TokenRouter {
/**
* @notice Initializes the Hyperlane router.
* @param _mailbox The address of the mailbox contract.
* @param _interchainGasPaymaster The address of the interchain gas paymaster contract.
*/
function initialize(address _mailbox, address _interchainGasPaymaster)
external
initializer
{
__HyperlaneConnectionClient_initialize(
_mailbox,
_interchainGasPaymaster
);
function initialize(address _mailbox) external initializer {
__Router_initialize(_mailbox);
}
function balanceOf(address _account) external view returns (uint256) {
function balanceOf(address _account)
external
view
override
returns (uint256)
{
return wrappedToken.balanceOf(_account);
}

@ -3,6 +3,8 @@ pragma solidity >=0.8.0;
import {TokenRouter} from "./libs/TokenRouter.sol";
import {IERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol";
import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import {ERC721EnumerableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol";
/**
@ -13,23 +15,18 @@ contract HypERC721 is ERC721EnumerableUpgradeable, TokenRouter {
/**
* @notice Initializes the Hyperlane router, ERC721 metadata, and mints initial supply to deployer.
* @param _mailbox The address of the mailbox contract.
* @param _interchainGasPaymaster The address of the interchain gas paymaster contract.
* @param _mintAmount The amount of NFTs to mint to `msg.sender`.
* @param _name The name of the token.
* @param _symbol The symbol of the token.
*/
function initialize(
address _mailbox,
address _interchainGasPaymaster,
uint256 _mintAmount,
string memory _name,
string memory _symbol
) external initializer {
// transfers ownership to `msg.sender`
__HyperlaneConnectionClient_initialize(
_mailbox,
_interchainGasPaymaster
);
__Router_initialize(_mailbox);
__ERC721_init(_name, _symbol);
for (uint256 i = 0; i < _mintAmount; i++) {
@ -37,6 +34,16 @@ contract HypERC721 is ERC721EnumerableUpgradeable, TokenRouter {
}
}
function balanceOf(address _account)
public
view
virtual
override(TokenRouter, ERC721Upgradeable, IERC721Upgradeable)
returns (uint256)
{
return ERC721Upgradeable.balanceOf(_account);
}
/**
* @dev Asserts `msg.sender` is owner and burns `_tokenId`.
* @inheritdoc TokenRouter

@ -28,19 +28,21 @@ contract HypERC721Collateral is TokenRouter {
/**
* @notice Initializes the Hyperlane router.
* @param _mailbox The address of the mailbox contract.
* @param _interchainGasPaymaster The address of the interchain gas paymaster contract.
*/
function initialize(address _mailbox, address _interchainGasPaymaster)
external
initializer
{
__HyperlaneConnectionClient_initialize(
_mailbox,
_interchainGasPaymaster
);
function initialize(address _mailbox) external initializer {
__Router_initialize(_mailbox);
}
function balanceOf(address _account) external view returns (uint256) {
/**
* @dev Returns the balance of `_account` for `wrappedToken`.
* @inheritdoc TokenRouter
*/
function balanceOf(address _account)
external
view
override
returns (uint256)
{
return IERC721(wrappedToken).balanceOf(_account);
}

@ -21,17 +21,10 @@ contract HypNative is TokenRouter {
/**
* @notice Initializes the Hyperlane router, ERC20 metadata, and mints initial supply to deployer.
* @param _mailbox The address of the mailbox contract.
* @param _interchainGasPaymaster The address of the interchain gas paymaster contract.
*/
function initialize(address _mailbox, address _interchainGasPaymaster)
external
initializer
{
function initialize(address _mailbox) external initializer {
// transfers ownership to `msg.sender`
__HyperlaneConnectionClient_initialize(
_mailbox,
_interchainGasPaymaster
);
__Router_initialize(_mailbox);
}
/**
@ -48,7 +41,12 @@ contract HypNative is TokenRouter {
return _transferRemote(_destination, _recipient, _amount, gasPayment);
}
function balanceOf(address _account) external view returns (uint256) {
function balanceOf(address _account)
external
view
override
returns (uint256)
{
return _account.balance;
}

@ -12,6 +12,15 @@ import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC72
* @author Abacus Works
*/
contract HypERC721URIStorage is HypERC721, ERC721URIStorageUpgradeable {
function balanceOf(address account)
public
view
override(HypERC721, ERC721Upgradeable)
returns (uint256)
{
return HypERC721.balanceOf(account);
}
/**
* @return _tokenURI The URI of `_tokenId`.
* @inheritdoc HypERC721

@ -92,6 +92,13 @@ abstract contract TokenRouter is GasRouter {
virtual
returns (bytes memory metadata);
/**
* @notice Returns the balance of `account` on this token router.
* @param account The address to query the balance of.
* @return The balance of `account`.
*/
function balanceOf(address account) external virtual returns (uint256);
/**
* @dev Mints tokens to recipient when router receives transfer message.
* @dev Emits `ReceivedTransferRemote` event on the destination chain.

@ -55,11 +55,11 @@
"scripts": {
"clean": "hardhat clean && rm -rf dist cache src/types && forge clean",
"docs": "forge doc",
"build": "hardhat compile && tsc",
"coverage": "hardhat coverage",
"build": "forge build",
"coverage": "forge coverage --report lcov",
"lint": "solhint contracts/**/*.sol && eslint . --ext .ts",
"prettier": "prettier --write ./contracts ./test",
"test": "hardhat test ./test/*.test.ts",
"test": "forge test -vvv",
"deploy-warp-route": "DEBUG=* ts-node scripts/deploy"
},
"types": "dist/index.d.ts"

@ -130,10 +130,7 @@ export class HypERC20Deployer extends GasRouterDeployer<
'HypERC20Collateral',
[config.token],
);
await this.multiProvider.handleTx(
chain,
router.initialize(config.mailbox, config.interchainGasPaymaster),
);
await this.multiProvider.handleTx(chain, router.initialize(config.mailbox));
return router;
}
@ -159,7 +156,7 @@ export class HypERC20Deployer extends GasRouterDeployer<
}
await this.multiProvider.handleTx(
chain,
router.initialize(config.mailbox, config.interchainGasPaymaster),
router.initialize(config.mailbox),
);
return router;
}
@ -178,7 +175,6 @@ export class HypERC20Deployer extends GasRouterDeployer<
chain,
router.initialize(
config.mailbox,
config.interchainGasPaymaster,
config.totalSupply,
config.name,
config.symbol,

@ -0,0 +1,369 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.13;
/*@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@ HYPERLANE @@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@*/
import "forge-std/Test.sol";
import {TypeCasts} from "@hyperlane-xyz/core/contracts/libs/TypeCasts.sol";
import {TestMailbox} from "@hyperlane-xyz/core/contracts/test/TestMailbox.sol";
import {TestPostDispatchHook} from "@hyperlane-xyz/core/contracts/test/TestPostDispatchHook.sol";
import {TestInterchainGasPaymaster} from "@hyperlane-xyz/core/contracts/test/TestInterchainGasPaymaster.sol";
import {GasRouter} from "@hyperlane-xyz/core/contracts/GasRouter.sol";
import {ERC20Test} from "../contracts/test/ERC20Test.sol";
import {HypERC20} from "../contracts/HypERC20.sol";
import {HypERC20Collateral} from "../contracts/HypERC20Collateral.sol";
import {HypNative} from "../contracts/HypNative.sol";
import {TokenRouter} from "../contracts/libs/TokenRouter.sol";
abstract contract HypTokenTest is Test {
using TypeCasts for address;
uint32 internal constant ORIGIN = 11;
uint32 internal constant DESTINATION = 12;
uint8 internal constant DECIMALS = 18;
uint256 internal constant TOTAL_SUPPLY = 1_000_000e18;
uint256 internal REQUIRED_VALUE; // initialized in setUp
uint256 internal constant GAS_LIMIT = 10_000;
uint256 internal IGP_GAS_PRICE; // initialized in test
uint256 internal constant TRANSFER_AMT = 100e18;
string internal constant NAME = "HyperlaneInu";
string internal constant SYMBOL = "HYP";
address internal constant ALICE = address(0x1);
address internal constant BOB = address(0x2);
ERC20Test internal primaryToken;
TokenRouter internal localToken;
HypERC20 internal remoteToken;
TestMailbox internal localMailbox;
TestMailbox internal remoteMailbox;
TestPostDispatchHook internal noopHook;
TestInterchainGasPaymaster internal igp;
event SentTransferRemote(
uint32 indexed destination,
bytes32 indexed recipient,
uint256 amount
);
event ReceivedTransferRemote(
uint32 indexed origin,
bytes32 indexed recipient,
uint256 amount
);
function setUp() public virtual {
localMailbox = new TestMailbox(ORIGIN);
remoteMailbox = new TestMailbox(DESTINATION);
primaryToken = new ERC20Test(NAME, SYMBOL, TOTAL_SUPPLY);
noopHook = new TestPostDispatchHook();
localMailbox.setDefaultHook(address(noopHook));
localMailbox.setRequiredHook(address(noopHook));
REQUIRED_VALUE = noopHook.quoteDispatch("", "");
remoteToken = new HypERC20(DECIMALS);
remoteToken.initialize(
address(remoteMailbox),
TOTAL_SUPPLY,
NAME,
SYMBOL
);
remoteToken.enrollRemoteRouter(
ORIGIN,
address(localToken).addressToBytes32()
);
igp = new TestInterchainGasPaymaster();
vm.deal(ALICE, 125000);
}
function _enrollRemoteTokenRouter() internal {
remoteToken.enrollRemoteRouter(
ORIGIN,
address(localToken).addressToBytes32()
);
}
function _expectRemoteBalance(address _user, uint256 _balance) internal {
assertEq(remoteToken.balanceOf(_user), _balance);
}
function _processTransfers(address _recipient, uint256 _amount) internal {
vm.prank(address(remoteMailbox));
remoteToken.handle(
ORIGIN,
address(localToken).addressToBytes32(),
abi.encodePacked(_recipient.addressToBytes32(), _amount)
);
}
function _setCustomGasConfig() internal {
localMailbox.setDefaultHook(address(igp));
IGP_GAS_PRICE = igp.gasPrice();
TokenRouter.GasRouterConfig[]
memory config = new TokenRouter.GasRouterConfig[](1);
config[0] = GasRouter.GasRouterConfig({
domain: DESTINATION,
gas: GAS_LIMIT
});
localToken.setDestinationGas(config);
}
function _performRemoteTransfer(uint256 _msgValue, uint256 _amount)
internal
{
vm.prank(ALICE);
localToken.transferRemote{value: _msgValue}(
DESTINATION,
BOB.addressToBytes32(),
_amount
);
vm.expectEmit(true, true, false, true);
emit ReceivedTransferRemote(ORIGIN, BOB.addressToBytes32(), _amount);
_processTransfers(BOB, _amount);
assertEq(remoteToken.balanceOf(BOB), _amount);
}
function _performRemoteTransferAndGas(
uint256 _msgValue,
uint256 _amount,
uint256 _gasOverhead
) internal {
uint256 ethBalance = ALICE.balance;
_performRemoteTransfer(_msgValue + _gasOverhead, _amount);
assertEq(ALICE.balance, ethBalance - REQUIRED_VALUE - _gasOverhead);
}
function _performRemoteTransferWithEmit(
uint256 _msgValue,
uint256 _amount,
uint256 _gasOverhead
) internal {
vm.expectEmit(true, true, false, true);
emit SentTransferRemote(DESTINATION, BOB.addressToBytes32(), _amount);
_performRemoteTransferAndGas(_msgValue, _amount, _gasOverhead);
}
function testBenchmark_overheadGasUsage() public {
vm.prank(address(localMailbox));
uint256 gasBefore = gasleft();
localToken.handle(
DESTINATION,
address(remoteToken).addressToBytes32(),
abi.encodePacked(BOB.addressToBytes32(), TRANSFER_AMT)
);
uint256 gasAfter = gasleft();
console.log("Overhead gas usage: %d", gasBefore - gasAfter);
}
}
contract HypERC20Test is HypTokenTest {
using TypeCasts for address;
HypERC20 internal erc20Token;
function setUp() public override {
super.setUp();
localToken = new HypERC20(DECIMALS);
erc20Token = HypERC20(address(localToken));
erc20Token.initialize(
address(localMailbox),
TOTAL_SUPPLY,
NAME,
SYMBOL
);
erc20Token.enrollRemoteRouter(
DESTINATION,
address(remoteToken).addressToBytes32()
);
erc20Token.transfer(ALICE, 1000e18);
_enrollRemoteTokenRouter();
}
function testInitialize_revert_ifAlreadyInitialized() public {
vm.expectRevert("Initializable: contract is already initialized");
erc20Token.initialize(ALICE, TOTAL_SUPPLY, NAME, SYMBOL);
}
function testTotalSupply() public {
assertEq(erc20Token.totalSupply(), TOTAL_SUPPLY);
}
function testDecimals() public {
assertEq(erc20Token.decimals(), DECIMALS);
}
function testLocalTransfers() public {
assertEq(erc20Token.balanceOf(ALICE), 1000e18);
assertEq(erc20Token.balanceOf(BOB), 0);
vm.prank(ALICE);
erc20Token.transfer(BOB, 100e18);
assertEq(erc20Token.balanceOf(ALICE), 900e18);
assertEq(erc20Token.balanceOf(BOB), 100e18);
}
function testRemoteTransfer() public {
remoteToken.enrollRemoteRouter(
ORIGIN,
address(localToken).addressToBytes32()
);
uint256 balanceBefore = erc20Token.balanceOf(ALICE);
_performRemoteTransferWithEmit(REQUIRED_VALUE, TRANSFER_AMT, 0);
assertEq(erc20Token.balanceOf(ALICE), balanceBefore - TRANSFER_AMT);
}
function testRemoteTransfer_invalidAmount() public {
vm.expectRevert("ERC20: burn amount exceeds balance");
_performRemoteTransfer(REQUIRED_VALUE, TRANSFER_AMT * 11);
assertEq(erc20Token.balanceOf(ALICE), 1000e18);
}
function testRemoteTransfer_withCustomGasConfig() public {
_setCustomGasConfig();
uint256 balanceBefore = erc20Token.balanceOf(ALICE);
_performRemoteTransferAndGas(
REQUIRED_VALUE,
TRANSFER_AMT,
GAS_LIMIT * IGP_GAS_PRICE
);
assertEq(erc20Token.balanceOf(ALICE), balanceBefore - TRANSFER_AMT);
}
}
contract HypERC20CollateralTest is HypTokenTest {
using TypeCasts for address;
HypERC20Collateral internal erc20Collateral;
function setUp() public override {
super.setUp();
localToken = new HypERC20Collateral(address(primaryToken));
erc20Collateral = HypERC20Collateral(address(localToken));
HypERC20Collateral(address(localToken)).initialize(
address(localMailbox)
);
erc20Collateral.enrollRemoteRouter(
DESTINATION,
address(remoteToken).addressToBytes32()
);
primaryToken.transfer(address(localToken), 1000e18);
primaryToken.transfer(ALICE, 1000e18);
_enrollRemoteTokenRouter();
}
function testInitialize_revert_ifAlreadyInitialized() public {
vm.expectRevert("Initializable: contract is already initialized");
erc20Collateral.initialize(ALICE);
}
function testRemoteTransfer() public {
uint256 balanceBefore = localToken.balanceOf(ALICE);
vm.prank(ALICE);
primaryToken.approve(address(localToken), TRANSFER_AMT);
_performRemoteTransferWithEmit(REQUIRED_VALUE, TRANSFER_AMT, 0);
assertEq(localToken.balanceOf(ALICE), balanceBefore - TRANSFER_AMT);
}
function testRemoteTransfer_invalidAllowance() public {
vm.expectRevert("ERC20: insufficient allowance");
_performRemoteTransfer(REQUIRED_VALUE, TRANSFER_AMT);
assertEq(localToken.balanceOf(ALICE), 1000e18);
}
function testRemoteTransfer_withCustomGasConfig() public {
_setCustomGasConfig();
uint256 balanceBefore = localToken.balanceOf(ALICE);
vm.prank(ALICE);
primaryToken.approve(address(localToken), TRANSFER_AMT);
_performRemoteTransferAndGas(
REQUIRED_VALUE,
TRANSFER_AMT,
GAS_LIMIT * IGP_GAS_PRICE
);
assertEq(localToken.balanceOf(ALICE), balanceBefore - TRANSFER_AMT);
}
}
contract HypNativeTest is HypTokenTest {
using TypeCasts for address;
HypNative internal nativeToken;
function setUp() public override {
super.setUp();
localToken = new HypNative();
nativeToken = HypNative(address(localToken));
nativeToken.initialize(address(localMailbox));
nativeToken.enrollRemoteRouter(
DESTINATION,
address(remoteToken).addressToBytes32()
);
vm.deal(address(localToken), 1000e18);
vm.deal(ALICE, 1000e18);
_enrollRemoteTokenRouter();
}
function testInitialize_revert_ifAlreadyInitialized() public {
vm.expectRevert("Initializable: contract is already initialized");
nativeToken.initialize(ALICE);
}
function testRemoteTransfer() public {
_performRemoteTransferWithEmit(
REQUIRED_VALUE,
TRANSFER_AMT,
TRANSFER_AMT
);
}
function testRemoteTransfer_invalidAmount() public {
vm.expectRevert("Native: amount exceeds msg.value");
_performRemoteTransfer(
REQUIRED_VALUE + TRANSFER_AMT,
TRANSFER_AMT * 10
);
assertEq(localToken.balanceOf(ALICE), 1000e18);
}
function testRemoteTransfer_withCustomGasConfig() public {
_setCustomGasConfig();
_performRemoteTransferAndGas(
REQUIRED_VALUE,
TRANSFER_AMT,
TRANSFER_AMT + GAS_LIMIT * IGP_GAS_PRICE
);
}
}

@ -0,0 +1,369 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.13;
/*@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@ HYPERLANE @@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@*/
import "forge-std/Test.sol";
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import {TestMailbox} from "@hyperlane-xyz/core/contracts/test/TestMailbox.sol";
import {TestPostDispatchHook} from "@hyperlane-xyz/core/contracts/test/TestPostDispatchHook.sol";
import {TypeCasts} from "@hyperlane-xyz/core/contracts/libs/TypeCasts.sol";
import {ERC721Test} from "../contracts/test/ERC721Test.sol";
import {TokenRouter} from "../contracts/libs/TokenRouter.sol";
import {HypERC721} from "../contracts/HypERC721.sol";
import {HypERC721Collateral} from "../contracts/HypERC721Collateral.sol";
import {HypERC721URIStorage} from "../contracts/extensions/HypERC721URIStorage.sol";
import {HypERC721URICollateral} from "../contracts/extensions/HypERC721URICollateral.sol";
import {ERC721URIStorageUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol";
abstract contract HypTokenTest is Test, IERC721Receiver {
using TypeCasts for address;
uint256 internal constant INITIAL_SUPPLY = 10;
string internal constant NAME = "Hyperlane Hedgehogs";
string internal constant SYMBOL = "HHH";
address internal constant ALICE = address(0x1);
address internal constant BOB = address(0x2);
uint32 internal constant ORIGIN = 11;
uint32 internal constant DESTINATION = 22;
uint256 internal constant TRANSFER_ID = 0;
string internal constant URI = "http://bit.ly/3reJLpx";
ERC721Test internal localPrimaryToken =
new ERC721Test(NAME, SYMBOL, INITIAL_SUPPLY * 2);
ERC721Test internal remotePrimaryToken =
new ERC721Test(NAME, SYMBOL, INITIAL_SUPPLY * 2);
TestMailbox internal localMailbox;
TestMailbox internal remoteMailbox;
TokenRouter internal localToken;
TokenRouter internal remoteToken;
TestPostDispatchHook internal noopHook;
event Dispatch(
address indexed sender,
uint32 indexed destination,
bytes32 indexed recipient,
bytes message
);
function setUp() public virtual {
noopHook = new TestPostDispatchHook();
localMailbox = new TestMailbox(ORIGIN);
localMailbox.setDefaultHook(address(noopHook));
localMailbox.setRequiredHook(address(noopHook));
remoteMailbox = new TestMailbox(DESTINATION);
remoteToken = new HypERC721Collateral(address(remotePrimaryToken));
}
function _deployRemoteToken(bool isCollateral) internal {
if (isCollateral) {
remoteToken = new HypERC721Collateral(address(remotePrimaryToken));
HypERC721Collateral(address(remoteToken)).initialize(
address(remoteMailbox)
);
remotePrimaryToken.transferFrom(
address(this),
address(remoteToken),
0
); // need for processing messages
} else {
remoteToken = new HypERC721();
HypERC721(address(remoteToken)).initialize(
address(remoteMailbox),
0,
NAME,
SYMBOL
);
}
remoteToken.enrollRemoteRouter(
ORIGIN,
address(localToken).addressToBytes32()
);
}
function _processTransfers(address _recipient, uint256 _tokenId) internal {
vm.prank(address(remoteMailbox));
remoteToken.handle(
ORIGIN,
address(localToken).addressToBytes32(),
abi.encodePacked(_recipient.addressToBytes32(), _tokenId)
);
}
function _performRemoteTransfer(uint256 _msgValue, uint256 _tokenId)
public
{
localToken.transferRemote{value: _msgValue}(
DESTINATION,
ALICE.addressToBytes32(),
_tokenId
);
_processTransfers(BOB, _tokenId);
assertEq(remoteToken.balanceOf(BOB), 1);
}
function testBenchmark_overheadGasUsage() public {
vm.prank(address(localMailbox));
uint256 gasBefore = gasleft();
localToken.handle(
DESTINATION,
address(remoteToken).addressToBytes32(),
abi.encodePacked(BOB.addressToBytes32(), INITIAL_SUPPLY + 1)
);
uint256 gasAfter = gasleft();
console.log("Overhead gas usage: %d", gasBefore - gasAfter);
}
function onERC721Received(
address, /*operator*/
address, /*from*/
uint256, /*tokenId*/
bytes calldata /*data*/
) external pure returns (bytes4) {
return IERC721Receiver.onERC721Received.selector;
}
}
contract HypERC721Test is HypTokenTest {
using TypeCasts for address;
HypERC721 internal hyp721;
function setUp() public virtual override {
super.setUp();
localToken = new HypERC721();
hyp721 = HypERC721(address(localToken));
hyp721.initialize(address(localMailbox), INITIAL_SUPPLY, NAME, SYMBOL);
hyp721.enrollRemoteRouter(
DESTINATION,
address(remoteToken).addressToBytes32()
);
}
function testInitialize_revert_ifAlreadyInitialized() public {
vm.expectRevert("Initializable: contract is already initialized");
hyp721.initialize(address(localMailbox), INITIAL_SUPPLY, NAME, SYMBOL);
}
function testTotalSupply() public {
assertEq(hyp721.balanceOf(address(this)), INITIAL_SUPPLY);
}
function testOwnerOf() public {
assertEq(hyp721.ownerOf(0), address(this));
}
function testLocalTransfer() public {
hyp721.transferFrom(address(this), ALICE, 0);
assertEq(hyp721.balanceOf(address(this)), INITIAL_SUPPLY - 1);
assertEq(hyp721.balanceOf(ALICE), 1);
}
function testLocalYTransfer_revert_invalidTokenId() public {
vm.expectRevert("ERC721: invalid token ID");
hyp721.transferFrom(address(this), ALICE, INITIAL_SUPPLY);
}
function testRemoteTransfer(bool isCollateral) public {
_deployRemoteToken(isCollateral);
_performRemoteTransfer(25000, 0);
assertEq(hyp721.balanceOf(address(this)), INITIAL_SUPPLY - 1);
}
function testRemoteTransfer_revert_unowned() public {
hyp721.transferFrom(address(this), BOB, 1);
_deployRemoteToken(false);
vm.expectRevert("!owner");
_performRemoteTransfer(25000, 1);
assertEq(hyp721.balanceOf(address(this)), INITIAL_SUPPLY - 1);
}
function testRemoteTransfer_revert_invalidTokenId() public {
_deployRemoteToken(false);
vm.expectRevert("ERC721: invalid token ID");
_performRemoteTransfer(25000, INITIAL_SUPPLY);
assertEq(hyp721.balanceOf(address(this)), INITIAL_SUPPLY);
}
}
contract MockHypERC721URIStorage is HypERC721URIStorage {
function setTokenURI(uint256 tokenId, string memory uri) public {
_setTokenURI(tokenId, uri);
}
}
contract HypERC721URIStorageTest is HypTokenTest {
using TypeCasts for address;
MockHypERC721URIStorage internal hyp721Storage;
function setUp() public override {
super.setUp();
localToken = new MockHypERC721URIStorage();
hyp721Storage = MockHypERC721URIStorage(address(localToken));
hyp721Storage.initialize(
address(localMailbox),
INITIAL_SUPPLY,
NAME,
SYMBOL
);
hyp721Storage.setTokenURI(0, URI);
hyp721Storage.enrollRemoteRouter(
DESTINATION,
address(remoteToken).addressToBytes32()
);
}
function testRemoteTransfers_revert_burned() public {
_deployRemoteToken(false);
_performRemoteTransfer(25000, 0);
assertEq(hyp721Storage.balanceOf(address(this)), INITIAL_SUPPLY - 1);
vm.expectRevert("ERC721: invalid token ID");
assertEq(hyp721Storage.tokenURI(0), "");
}
}
contract HypERC721CollateralTest is HypTokenTest {
using TypeCasts for address;
HypERC721Collateral internal hyp721Collateral;
function setUp() public override {
super.setUp();
localToken = new HypERC721Collateral(address(localPrimaryToken));
hyp721Collateral = HypERC721Collateral(address(localToken));
hyp721Collateral.initialize(address(localMailbox));
hyp721Collateral.enrollRemoteRouter(
DESTINATION,
address(remoteToken).addressToBytes32()
);
localPrimaryToken.transferFrom(
address(this),
address(hyp721Collateral),
INITIAL_SUPPLY + 1
);
}
function testInitialize_revert_ifAlreadyInitialized() public {
vm.expectRevert("Initializable: contract is already initialized");
hyp721Collateral.initialize(ALICE);
}
function testRemoteTransfer(bool isCollateral) public {
localPrimaryToken.approve(address(hyp721Collateral), 0);
_deployRemoteToken(isCollateral);
_performRemoteTransfer(25000, 0);
assertEq(
hyp721Collateral.balanceOf(address(this)),
INITIAL_SUPPLY * 2 - 2
);
}
function testRemoteTransfer_revert_unowned() public {
localPrimaryToken.transferFrom(address(this), BOB, 1);
_deployRemoteToken(false);
vm.expectRevert("ERC721: caller is not token owner or approved");
_performRemoteTransfer(25000, 1);
assertEq(
hyp721Collateral.balanceOf(address(this)),
INITIAL_SUPPLY * 2 - 2
);
}
function testRemoteTransfer_revert_invalidTokenId() public {
_deployRemoteToken(false);
vm.expectRevert("ERC721: invalid token ID");
_performRemoteTransfer(25000, INITIAL_SUPPLY * 2);
assertEq(
hyp721Collateral.balanceOf(address(this)),
INITIAL_SUPPLY * 2 - 1
);
}
}
contract HypERC721CollateralURIStorageTest is HypTokenTest {
using TypeCasts for address;
HypERC721URICollateral internal hyp721URICollateral;
function setUp() public override {
super.setUp();
localToken = new HypERC721URICollateral(address(localPrimaryToken));
hyp721URICollateral = HypERC721URICollateral(address(localToken));
hyp721URICollateral.initialize(address(localMailbox));
hyp721URICollateral.enrollRemoteRouter(
DESTINATION,
address(remoteToken).addressToBytes32()
);
// localPrimaryToken.setTokenURI(0, URI);
localPrimaryToken.transferFrom(
address(this),
address(hyp721URICollateral),
INITIAL_SUPPLY + 1
);
localPrimaryToken.ownerOf(0);
}
function testRemoteTransfers_revert_burned() public {
_deployRemoteToken(false);
localPrimaryToken.approve(address(hyp721URICollateral), 0);
bytes
memory message = hex"03000000000000000b0000000000000000000000001d1499e622d69689cdf9004d05ec547d650ff21100000016000000000000000000000000a0cb889707d426a7a386870a03bc70d1b069759800000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000544553542d424153452d55524930";
vm.expectEmit(true, true, false, true);
emit Dispatch(
address(localToken),
DESTINATION,
address(remoteToken).addressToBytes32(),
message
);
localToken.transferRemote{value: 25000}(
DESTINATION,
BOB.addressToBytes32(),
TRANSFER_ID
);
_processTransfers(BOB, 0);
assertEq(remoteToken.balanceOf(BOB), 1);
assertEq(
hyp721URICollateral.balanceOf(address(this)),
INITIAL_SUPPLY * 2 - 2
);
}
}

@ -1,294 +0,0 @@
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import '@nomiclabs/hardhat-waffle';
import { expect } from 'chai';
import { BigNumber, BigNumberish } from 'ethers';
import { ethers } from 'hardhat';
import { InterchainGasPaymaster__factory } from '@hyperlane-xyz/core';
import {
ChainMap,
Chains,
HyperlaneContractsMap,
MultiProvider,
RouterConfig,
TestCoreApp,
TestCoreDeployer,
deployTestIgpsAndGetRouterConfig,
} from '@hyperlane-xyz/sdk';
import { addressToBytes32, objMap } from '@hyperlane-xyz/utils';
import { TokenConfig, TokenType } from '../src/config';
import { HypERC20Factories } from '../src/contracts';
import { HypERC20Deployer } from '../src/deploy';
import {
ERC20,
ERC20Test__factory,
ERC20__factory,
HypERC20,
HypERC20Collateral,
HypNative,
} from '../src/types';
const localChain = Chains.test1;
const remoteChain = Chains.test2;
let localDomain: number;
let remoteDomain: number;
const totalSupply = 3000;
const amount = 10;
const tokenMetadata = {
name: 'HypERC20',
symbol: 'HYP',
decimals: 18,
totalSupply,
};
for (const variant of [
TokenType.synthetic,
TokenType.collateral,
TokenType.native,
]) {
describe(`HypERC20${variant}`, async () => {
let owner: SignerWithAddress;
let recipient: SignerWithAddress;
let core: TestCoreApp;
let deployer: HypERC20Deployer;
let contracts: HyperlaneContractsMap<HypERC20Factories>;
let localTokenConfig: TokenConfig;
let local: HypERC20 | HypERC20Collateral | HypNative;
let remote: HypERC20;
let interchainGasPayment: BigNumber;
beforeEach(async () => {
[owner, recipient] = await ethers.getSigners();
const multiProvider = MultiProvider.createTestMultiProvider({
signer: owner,
});
localDomain = multiProvider.getDomainId(localChain);
remoteDomain = multiProvider.getDomainId(remoteChain);
const coreDeployer = new TestCoreDeployer(multiProvider);
const coreContractsMaps = await coreDeployer.deploy();
core = new TestCoreApp(coreContractsMaps, multiProvider);
const routerConfig = await deployTestIgpsAndGetRouterConfig(
multiProvider,
owner.address,
core.contractsMap,
);
let erc20: ERC20 | undefined;
if (variant === TokenType.collateral) {
erc20 = await new ERC20Test__factory(owner).deploy(
tokenMetadata.name,
tokenMetadata.symbol,
tokenMetadata.totalSupply,
tokenMetadata.decimals,
);
localTokenConfig = {
type: variant,
token: erc20.address,
};
} else if (variant === TokenType.native) {
localTokenConfig = {
type: variant,
};
} else if (variant === TokenType.synthetic) {
localTokenConfig = { type: variant, ...tokenMetadata };
}
const config = objMap(routerConfig, (key) => ({
...routerConfig[key],
...(key === localChain
? localTokenConfig
: { type: TokenType.synthetic }),
owner: owner.address,
})) as ChainMap<TokenConfig & RouterConfig>;
deployer = new HypERC20Deployer(multiProvider);
contracts = await deployer.deploy(config);
local = contracts[localChain].router;
interchainGasPayment = await local.quoteGasPayment(remoteDomain);
if (variant === TokenType.native) {
interchainGasPayment = interchainGasPayment.add(amount);
}
if (variant === TokenType.collateral) {
await erc20!.approve(local.address, amount);
}
remote = contracts[remoteChain].router as HypERC20;
});
it('should not be initializable again', async () => {
const initializeTx =
variant === TokenType.collateral || variant === TokenType.native
? (local as HypERC20Collateral).initialize(
ethers.constants.AddressZero,
ethers.constants.AddressZero,
)
: (local as HypERC20).initialize(
ethers.constants.AddressZero,
ethers.constants.AddressZero,
0,
'',
'',
);
await expect(initializeTx).to.be.revertedWith(
'Initializable: contract is already initialized',
);
});
if (variant === TokenType.synthetic) {
it('should mint total supply to deployer', async () => {
await expectBalance(local, recipient, 0);
await expectBalance(local, owner, totalSupply);
await expectBalance(remote, recipient, 0);
await expectBalance(remote, owner, totalSupply);
});
it('should allow for local transfers', async () => {
await (local as HypERC20).transfer(recipient.address, amount);
await expectBalance(local, recipient, amount);
await expectBalance(local, owner, totalSupply - amount);
await expectBalance(remote, recipient, 0);
await expectBalance(remote, owner, totalSupply);
});
}
it('benchmark handle gas overhead', async () => {
const localRaw = local.connect(ethers.provider);
const mailboxAddress = core.contractsMap[localChain].mailbox.address;
if (variant === TokenType.collateral) {
const tokenAddress = await (local as HypERC20Collateral).wrappedToken();
const token = ERC20__factory.connect(tokenAddress, owner);
await token.transfer(local.address, totalSupply);
} else if (variant === TokenType.native) {
const remoteDomain = core.multiProvider.getDomainId(remoteChain);
// deposit amount
await local.transferRemote(
remoteDomain,
addressToBytes32(remote.address),
amount,
{ value: interchainGasPayment },
);
}
const message = `${addressToBytes32(recipient.address)}${BigNumber.from(
amount,
)
.toHexString()
.slice(2)
.padStart(64, '0')}`;
const handleGas = await localRaw.estimateGas.handle(
remoteDomain,
addressToBytes32(remote.address),
message,
{ from: mailboxAddress },
);
console.log(handleGas);
});
it('should allow for remote transfers', async () => {
const localOwner = await local.balanceOf(owner.address);
const localRecipient = await local.balanceOf(recipient.address);
const remoteOwner = await remote.balanceOf(owner.address);
const remoteRecipient = await remote.balanceOf(recipient.address);
await local.transferRemote(
remoteDomain,
addressToBytes32(recipient.address),
amount,
{
value: interchainGasPayment,
},
);
let expectedLocal = localOwner.sub(amount);
await expectBalance(local, recipient, localRecipient);
if (variant === TokenType.native) {
// account for tx fees, rewards, etc.
expectedLocal = await local.balanceOf(owner.address);
}
await expectBalance(local, owner, expectedLocal);
await expectBalance(remote, recipient, remoteRecipient);
await expectBalance(remote, owner, remoteOwner);
await core.processMessages();
await expectBalance(local, recipient, localRecipient);
if (variant === TokenType.native) {
// account for tx fees, rewards, etc.
expectedLocal = await local.balanceOf(owner.address);
}
await expectBalance(local, owner, expectedLocal);
await expectBalance(remote, recipient, remoteRecipient.add(amount));
await expectBalance(remote, owner, remoteOwner);
});
it('allows interchain gas payment for remote transfers', async () => {
const interchainGasPaymaster = new InterchainGasPaymaster__factory()
.attach(await local.interchainGasPaymaster())
.connect(owner);
await expect(
local.transferRemote(
remoteDomain,
addressToBytes32(recipient.address),
amount,
{ value: interchainGasPayment },
),
).to.emit(interchainGasPaymaster, 'GasPayment');
});
it('should prevent remote transfer of unowned balance', async () => {
const revertReason = (): string => {
switch (variant) {
case TokenType.synthetic:
return 'ERC20: burn amount exceeds balance';
case TokenType.collateral:
return 'ERC20: insufficient allowance';
case TokenType.native:
return 'Native: amount exceeds msg.value';
}
return '';
};
const value =
variant === TokenType.native ? amount - 1 : interchainGasPayment;
await expect(
local
.connect(recipient)
.transferRemote(
remoteDomain,
addressToBytes32(recipient.address),
amount,
{ value },
),
).to.be.revertedWith(revertReason());
});
it('should emit TransferRemote events', async () => {
expect(
await local.transferRemote(
remoteDomain,
addressToBytes32(recipient.address),
amount,
{ value: interchainGasPayment },
),
)
.to.emit(local, 'SentTransferRemote')
.withArgs(remoteDomain, recipient.address, amount);
expect(await core.processMessages())
.to.emit(local, 'ReceivedTransferRemote')
.withArgs(localDomain, recipient.address, amount);
});
});
}
const expectBalance = async (
token: HypERC20 | HypERC20Collateral | ERC20 | HypNative,
signer: SignerWithAddress,
balance: BigNumberish,
) => {
return expect(await token.balanceOf(signer.address)).to.eq(balance);
};

@ -1,323 +0,0 @@
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import '@nomiclabs/hardhat-waffle';
import { expect } from 'chai';
import { BigNumber, BigNumberish } from 'ethers';
import { ethers } from 'hardhat';
import { InterchainGasPaymaster__factory } from '@hyperlane-xyz/core';
import {
Chains,
HyperlaneContractsMap,
MultiProvider,
TestCoreApp,
TestCoreDeployer,
deployTestIgpsAndGetRouterConfig,
} from '@hyperlane-xyz/sdk';
import { addressToBytes32, objMap } from '@hyperlane-xyz/utils';
import { TokenConfig, TokenType } from '../src/config';
import { HypERC721Factories } from '../src/contracts';
import { HypERC721Deployer } from '../src/deploy';
import {
ERC721,
ERC721Test__factory,
ERC721__factory,
HypERC721,
HypERC721Collateral,
HypERC721URICollateral,
HypERC721URIStorage,
} from '../src/types';
const localChain = Chains.test1;
const remoteChain = Chains.test2;
let localDomain: number;
let remoteDomain: number;
const totalSupply = 50;
const tokenId = 10;
const tokenId2 = 20;
const tokenId3 = 30;
const tokenId4 = 40;
const tokenMetadata = {
name: 'HypERC721',
symbol: 'HYP',
totalSupply,
};
for (const withCollateral of [true, false]) {
for (const withUri of [true, false]) {
const tokenConfig: TokenConfig = {
type: withUri ? TokenType.syntheticUri : TokenType.synthetic,
...tokenMetadata,
};
const configMap = {
test1: tokenConfig,
test2: {
...tokenConfig,
totalSupply: 0,
},
test3: {
...tokenConfig,
totalSupply: 0,
},
};
describe(`HypERC721${withUri ? 'URI' : ''}${
withCollateral ? 'Collateral' : ''
}`, async () => {
let owner: SignerWithAddress;
let recipient: SignerWithAddress;
let core: TestCoreApp;
let deployer: HypERC721Deployer;
let contracts: HyperlaneContractsMap<HypERC721Factories>;
let local: HypERC721 | HypERC721Collateral | HypERC721URICollateral;
let remote: HypERC721 | HypERC721Collateral | HypERC721URIStorage;
let interchainGasPayment: BigNumberish;
beforeEach(async () => {
[owner, recipient] = await ethers.getSigners();
const multiProvider = MultiProvider.createTestMultiProvider({
signer: owner,
});
localDomain = multiProvider.getDomainId(localChain);
remoteDomain = multiProvider.getDomainId(remoteChain);
const coreDeployer = new TestCoreDeployer(multiProvider);
const coreContractsMaps = await coreDeployer.deploy();
core = new TestCoreApp(coreContractsMaps, multiProvider);
const coreConfig = await deployTestIgpsAndGetRouterConfig(
multiProvider,
owner.address,
core.contractsMap,
);
const configWithTokenInfo = objMap(coreConfig, (key) => ({
...coreConfig[key],
...configMap[key],
owner: owner.address,
}));
let erc721: ERC721 | undefined;
if (withCollateral) {
erc721 = await new ERC721Test__factory(owner).deploy(
tokenConfig.name,
tokenConfig.symbol,
tokenConfig.totalSupply,
);
configWithTokenInfo.test1 = {
type: withUri ? TokenType.collateralUri : TokenType.collateral,
token: erc721.address,
...coreConfig.test1,
};
}
deployer = new HypERC721Deployer(multiProvider);
contracts = await deployer.deploy(configWithTokenInfo);
local = contracts[localChain].router;
if (withCollateral) {
// approve wrapper to transfer tokens
await erc721!.approve(local.address, tokenId);
await erc721!.approve(local.address, tokenId2);
await erc721!.approve(local.address, tokenId3);
await erc721!.approve(local.address, tokenId4);
}
interchainGasPayment = await local.quoteGasPayment(remoteDomain);
remote = contracts[remoteChain].router;
});
it('should not be initializable again', async () => {
const initializeTx = withCollateral
? (local as HypERC721Collateral).initialize(
ethers.constants.AddressZero,
ethers.constants.AddressZero,
)
: (local as HypERC721).initialize(
ethers.constants.AddressZero,
ethers.constants.AddressZero,
0,
'',
'',
);
await expect(initializeTx).to.be.revertedWith(
'Initializable: contract is already initialized',
);
});
it('should mint total supply to deployer on local domain', async () => {
await expectBalance(local, recipient, 0);
await expectBalance(local, owner, totalSupply);
await expectBalance(remote, recipient, 0);
await expectBalance(remote, owner, 0);
});
// do not test underlying ERC721 collateral functionality
if (!withCollateral) {
it('should allow for local transfers', async () => {
await (local as HypERC721).transferFrom(
owner.address,
recipient.address,
tokenId,
);
await expectBalance(local, recipient, 1);
await expectBalance(local, owner, totalSupply - 1);
await expectBalance(remote, recipient, 0);
await expectBalance(remote, owner, 0);
});
}
it('should not allow transfers of nonexistent identifiers', async () => {
const invalidTokenId = totalSupply + 10;
if (!withCollateral) {
await expect(
(local as HypERC721).transferFrom(
owner.address,
recipient.address,
invalidTokenId,
),
).to.be.revertedWith('ERC721: invalid token ID');
}
await expect(
local.transferRemote(
remoteDomain,
addressToBytes32(recipient.address),
invalidTokenId,
{ value: interchainGasPayment },
),
).to.be.revertedWith('ERC721: invalid token ID');
});
it('should allow for remote transfers', async () => {
await local.transferRemote(
remoteDomain,
addressToBytes32(recipient.address),
tokenId2,
{ value: interchainGasPayment },
);
await expectBalance(local, recipient, 0);
await expectBalance(local, owner, totalSupply - 1);
await expectBalance(remote, recipient, 0);
await expectBalance(remote, owner, 0);
await core.processMessages();
await expectBalance(local, recipient, 0);
await expectBalance(local, owner, totalSupply - 1);
await expectBalance(remote, recipient, 1);
await expectBalance(remote, owner, 0);
});
if (withUri && withCollateral) {
it('should relay URI with remote transfer', async () => {
const remoteUri = remote as HypERC721URIStorage;
await expect(remoteUri.tokenURI(tokenId2)).to.be.revertedWith('');
await local.transferRemote(
remoteDomain,
addressToBytes32(recipient.address),
tokenId2,
{ value: interchainGasPayment },
);
await expect(remoteUri.tokenURI(tokenId2)).to.be.revertedWith('');
await core.processMessages();
expect(await remoteUri.tokenURI(tokenId2)).to.equal(
`TEST-BASE-URI${tokenId2}`,
);
});
}
it('should prevent remote transfer of unowned id', async () => {
const revertReason = withCollateral
? 'ERC721: transfer from incorrect owner'
: '!owner';
await expect(
local
.connect(recipient)
.transferRemote(
remoteDomain,
addressToBytes32(recipient.address),
tokenId2,
{ value: interchainGasPayment },
),
).to.be.revertedWith(revertReason);
});
it('benchmark handle gas overhead', async () => {
const localRaw = local.connect(ethers.provider);
const mailboxAddress = core.contractsMap[localChain].mailbox.address;
let tokenIdToUse: number;
if (withCollateral) {
const tokenAddress = await (
local as HypERC721Collateral
).wrappedToken();
const token = ERC721__factory.connect(tokenAddress, owner);
await token.transferFrom(owner.address, local.address, tokenId);
tokenIdToUse = tokenId;
} else {
tokenIdToUse = totalSupply + 1;
}
const message = `${addressToBytes32(recipient.address)}${BigNumber.from(
tokenIdToUse,
)
.toHexString()
.slice(2)
.padStart(64, '0')}`;
try {
const gas = await localRaw.estimateGas.handle(
remoteDomain,
addressToBytes32(remote.address),
message,
{ from: mailboxAddress },
);
console.log(gas);
} catch (e) {
console.log('FAILED');
}
});
it('allows interchain gas payment for remote transfers', async () => {
const interchainGasPaymaster = new InterchainGasPaymaster__factory()
.attach(await local.interchainGasPaymaster())
.connect(owner);
await expect(
local.transferRemote(
remoteDomain,
addressToBytes32(recipient.address),
tokenId3,
{
value: interchainGasPayment,
},
),
).to.emit(interchainGasPaymaster, 'GasPayment');
});
it('should emit TransferRemote events', async () => {
expect(
await local.transferRemote(
remoteDomain,
addressToBytes32(recipient.address),
tokenId4,
{ value: interchainGasPayment },
),
)
.to.emit(local, 'SentTransferRemote')
.withArgs(remoteDomain, recipient.address, tokenId4);
expect(await core.processMessages())
.to.emit(local, 'ReceivedTransferRemote')
.withArgs(localDomain, recipient.address, tokenId4);
});
});
}
}
const expectBalance = async (
token: HypERC721 | HypERC721Collateral | ERC721,
signer: SignerWithAddress,
balance: number,
) => {
expect(await token.balanceOf(signer.address)).to.eq(balance);
};
Loading…
Cancel
Save