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
parent
7309f770ef
commit
f4b12a438b
@ -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…
Reference in new issue