Add URI support (#12)

* Add HypERC721URIStorage

* Add HypERC721URICollateral
pull/2435/head
Yorke Rhodes 2 years ago committed by GitHub
parent cb0ba36841
commit 272a195149
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 28
      contracts/HypERC20.sol
  2. 37
      contracts/HypERC20Collateral.sol
  3. 29
      contracts/HypERC721.sol
  4. 39
      contracts/HypERC721Collateral.sol
  5. 29
      contracts/extensions/HypERC721URICollateral.sol
  6. 73
      contracts/extensions/HypERC721URIStorage.sol
  7. 20
      contracts/libs/Message.sol
  8. 42
      contracts/libs/TokenRouter.sol
  9. 4
      contracts/test/ERC721Test.sol
  10. 28
      src/config.ts
  11. 3
      src/contracts.ts
  12. 11
      src/deploy.ts
  13. 27
      test/erc20.test.ts
  14. 387
      test/erc721.test.ts

@ -6,7 +6,7 @@ import {TokenRouter} from "./libs/TokenRouter.sol";
import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
/**
* @title Hyperlane Token that extends the ERC20 token standard to enable native interchain transfers.
* @title Hyperlane ERC20 Token Router that extends ERC20 with remote transfer functionality.
* @author Abacus Works
* @dev Supply on each chain is not constant but the aggregate supply across all chains is.
*/
@ -40,16 +40,28 @@ contract HypERC20 is ERC20Upgradeable, TokenRouter {
_mint(msg.sender, _totalSupply);
}
// called in `TokenRouter.transferRemote` before `Mailbox.dispatch`
function _transferFromSender(uint256 _amount) internal override {
_burn(msg.sender, _amount);
}
// called by `TokenRouter.handle`
function _transferTo(address _recipient, uint256 _amount)
/**
* @dev Burns `_amount` of token from `msg.sender` balance.
* @inheritdoc TokenRouter
*/
function _transferFromSender(uint256 _amount)
internal
override
returns (bytes memory)
{
_burn(msg.sender, _amount);
return bytes(""); // no metadata
}
/**
* @dev Mints `_amount` of token to `_recipient` balance.
* @inheritdoc TokenRouter
*/
function _transferTo(
address _recipient,
uint256 _amount,
bytes calldata // no metadata
) internal override {
_mint(_recipient, _amount);
}
}

@ -6,7 +6,7 @@ import {TokenRouter} from "./libs/TokenRouter.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @title Collateralize ERC20 token and route messages to HypERC20 tokens.
* @title Hyperlane ERC20 Token Collateral that wraps an existing ERC20 with remote transfer functionality.
* @author Abacus Works
*/
contract HypERC20Collateral is TokenRouter {
@ -16,6 +16,12 @@ contract HypERC20Collateral is TokenRouter {
wrappedToken = IERC20(erc20);
}
/**
* @notice Initializes the Hyperlane router.
* @param _mailbox The address of the mailbox contract.
* @param _interchainGasPaymaster The address of the interchain gas paymaster contract.
* @param _interchainSecurityModule The address of the interchain security module contract.
*/
function initialize(
address _mailbox,
address _interchainGasPaymaster,
@ -28,14 +34,31 @@ contract HypERC20Collateral is TokenRouter {
);
}
function _transferFromSender(uint256 _amount) internal override {
require(wrappedToken.transferFrom(msg.sender, address(this), _amount));
}
function _transferTo(address _recipient, uint256 _amount)
/**
* @dev Transfers `_amount` of `wrappedToken` from `msg.sender` to this contract.
* @inheritdoc TokenRouter
*/
function _transferFromSender(uint256 _amount)
internal
override
returns (bytes memory)
{
require(wrappedToken.transfer(_recipient, _amount));
require(
wrappedToken.transferFrom(msg.sender, address(this), _amount),
"!transferFrom"
);
return bytes(""); // no metadata
}
/**
* @dev Transfers `_amount` of `wrappedToken` from this contract to `_recipient`.
* @inheritdoc TokenRouter
*/
function _transferTo(
address _recipient,
uint256 _amount,
bytes calldata // no metadata
) internal override {
require(wrappedToken.transfer(_recipient, _amount), "!transfer");
}
}

@ -6,7 +6,7 @@ import {TokenRouter} from "./libs/TokenRouter.sol";
import {ERC721EnumerableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol";
/**
* @title Hyperlane Token that extends the ERC721 token standard to enable native interchain transfers.
* @title Hyperlane ERC721 Token Router that extends ERC721 with remote transfer functionality.
* @author Abacus Works
*/
contract HypERC721 is ERC721EnumerableUpgradeable, TokenRouter {
@ -40,17 +40,30 @@ contract HypERC721 is ERC721EnumerableUpgradeable, TokenRouter {
}
}
// called in `TokenRouter.transferRemote` before `Mailbox.dispatch`
function _transferFromSender(uint256 _tokenId) internal override {
/**
* @dev Asserts `msg.sender` is owner and burns `_tokenId`.
* @inheritdoc TokenRouter
*/
function _transferFromSender(uint256 _tokenId)
internal
virtual
override
returns (bytes memory)
{
require(ownerOf(_tokenId) == msg.sender, "!owner");
_burn(_tokenId);
return bytes(""); // no metadata
}
// called by `TokenRouter.handle`
function _transferTo(address _recipient, uint256 _tokenId)
internal
override
{
/**
* @dev Mints `_tokenId` to `_recipient`.
* @inheritdoc TokenRouter
*/
function _transferTo(
address _recipient,
uint256 _tokenId,
bytes calldata // no metadata
) internal virtual override {
_mint(_recipient, _tokenId);
}
}

@ -6,16 +6,22 @@ import {TokenRouter} from "./libs/TokenRouter.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
/**
* @title Collateralize ERC20 token and route messages to HypERC20 tokens.
* @title Hyperlane ERC721 Token Collateral that wraps an existing ERC721 with remote transfer functionality.
* @author Abacus Works
*/
contract HypERC721Collateral is TokenRouter {
IERC721 public immutable wrappedToken;
address public immutable wrappedToken;
constructor(address erc721) {
wrappedToken = IERC721(erc721);
wrappedToken = erc721;
}
/**
* @notice Initializes the Hyperlane router.
* @param _mailbox The address of the mailbox contract.
* @param _interchainGasPaymaster The address of the interchain gas paymaster contract.
* @param _interchainSecurityModule The address of the interchain security module contract.
*/
function initialize(
address _mailbox,
address _interchainGasPaymaster,
@ -28,14 +34,29 @@ contract HypERC721Collateral is TokenRouter {
);
}
function _transferFromSender(uint256 _amount) internal override {
wrappedToken.transferFrom(msg.sender, address(this), _amount);
}
function _transferTo(address _recipient, uint256 _amount)
/**
* @dev Transfers `_tokenId` of `wrappedToken` from `msg.sender` to this contract.
* @inheritdoc TokenRouter
*/
function _transferFromSender(uint256 _tokenId)
internal
virtual
override
returns (bytes memory)
{
wrappedToken.transferFrom(address(this), _recipient, _amount);
IERC721(wrappedToken).transferFrom(msg.sender, address(this), _tokenId);
return bytes(""); // no metadata
}
/**
* @dev Transfers `_tokenId` of `wrappedToken` from this contract to `_recipient`.
* @inheritdoc TokenRouter
*/
function _transferTo(
address _recipient,
uint256 _tokenId,
bytes calldata // no metadata
) internal override {
IERC721(wrappedToken).transferFrom(address(this), _recipient, _tokenId);
}
}

@ -0,0 +1,29 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.0;
import {HypERC721Collateral} from "../HypERC721Collateral.sol";
import {IERC721MetadataUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/IERC721MetadataUpgradeable.sol";
/**
* @title Hyperlane ERC721 Token Collateral that wraps an existing ERC721 with remote transfer and URI relay functionality.
* @author Abacus Works
*/
contract HypERC721URICollateral is HypERC721Collateral {
constructor(address erc721) HypERC721Collateral(erc721) {}
/**
* @dev Transfers `_tokenId` of `wrappedToken` from `msg.sender` to this contract.
* @return The URI of `_tokenId` on `wrappedToken`.
* @inheritdoc HypERC721Collateral
*/
function _transferFromSender(uint256 _tokenId)
internal
override
returns (bytes memory)
{
HypERC721Collateral._transferFromSender(_tokenId);
return
bytes(IERC721MetadataUpgradeable(wrappedToken).tokenURI(_tokenId));
}
}

@ -0,0 +1,73 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.0;
import {HypERC721} from "../HypERC721.sol";
import {ERC721URIStorageUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol";
import {ERC721EnumerableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol";
import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
/**
* @title Hyperlane ERC721 Token that extends ERC721URIStorage with remote transfer and URI relay functionality.
* @author Abacus Works
*/
contract HypERC721URIStorage is HypERC721, ERC721URIStorageUpgradeable {
/**
* @return _tokenURI The URI of `_tokenId`.
* @inheritdoc HypERC721
*/
function _transferFromSender(uint256 _tokenId)
internal
override
returns (bytes memory _tokenURI)
{
_tokenURI = bytes(tokenURI(_tokenId)); // requires minted
HypERC721._transferFromSender(_tokenId);
}
/**
* @dev Sets the URI for `_tokenId` to `_tokenURI`.
* @inheritdoc HypERC721
*/
function _transferTo(
address _recipient,
uint256 _tokenId,
bytes calldata _tokenURI
) internal override {
HypERC721._transferTo(_recipient, _tokenId, _tokenURI);
_setTokenURI(_tokenId, string(_tokenURI)); // requires minted
}
function tokenURI(uint256 tokenId)
public
view
override(ERC721Upgradeable, ERC721URIStorageUpgradeable)
returns (string memory)
{
return ERC721URIStorageUpgradeable.tokenURI(tokenId);
}
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal override(ERC721EnumerableUpgradeable, ERC721Upgradeable) {
ERC721EnumerableUpgradeable._beforeTokenTransfer(from, to, tokenId);
}
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721EnumerableUpgradeable, ERC721Upgradeable)
returns (bool)
{
return ERC721EnumerableUpgradeable.supportsInterface(interfaceId);
}
function _burn(uint256 tokenId)
internal
override(ERC721URIStorageUpgradeable, ERC721Upgradeable)
{
ERC721URIStorageUpgradeable._burn(tokenId);
}
}

@ -2,12 +2,12 @@
pragma solidity >=0.8.0;
library Message {
function format(bytes32 _recipient, uint256 _amount)
internal
pure
returns (bytes memory)
{
return abi.encodePacked(_recipient, _amount);
function format(
bytes32 _recipient,
uint256 _amount,
bytes memory _metadata
) internal pure returns (bytes memory) {
return abi.encodePacked(_recipient, _amount, _metadata);
}
function recipient(bytes calldata message) internal pure returns (bytes32) {
@ -22,4 +22,12 @@ library Message {
function tokenId(bytes calldata message) internal pure returns (uint256) {
return amount(message);
}
function metadata(bytes calldata message)
internal
pure
returns (bytes calldata)
{
return message[64:];
}
}

@ -6,9 +6,8 @@ import {TypeCasts} from "@hyperlane-xyz/core/contracts/libs/TypeCasts.sol";
import {Message} from "./Message.sol";
/**
* @title Hyperlane Token that extends the ERC20 token standard to enable native interchain transfers.
* @title Hyperlane Token Router that extends Router with abstract token (ERC20/ERC721) remote transfer functionality.
* @author Abacus Works
* @dev Supply on each chain is not constant but the aggregate supply across all chains is.
*/
abstract contract TokenRouter is Router {
using TypeCasts for bytes32;
@ -39,29 +38,36 @@ abstract contract TokenRouter is Router {
);
/**
* @notice Transfers `_amount` of tokens from `msg.sender` to `_recipient` on the `_destination` chain.
* @dev Burns `_amount` of tokens from `msg.sender` on the origin chain and dispatches
* message to the `destination` chain to mint `_amount` of tokens to `recipient`.
* @notice Transfers `_amountOrId` token to `_recipient` on `_destination` domain.
* @dev Delegates transfer logic to `_transferFromSender` implementation.
* @dev Emits `SentTransferRemote` event on the origin chain.
* @param _destination The identifier of the destination chain.
* @param _recipient The address of the recipient on the destination chain.
* @param _amount The amount of tokens to be sent to the remote recipient.
* @param _amountOrId The amount or identifier of tokens to be sent to the remote recipient.
*/
function transferRemote(
uint32 _destination,
bytes32 _recipient,
uint256 _amount
uint256 _amountOrId
) external payable {
_transferFromSender(_amount);
bytes memory metadata = _transferFromSender(_amountOrId);
_dispatchWithGas(
_destination,
Message.format(_recipient, _amount),
Message.format(_recipient, _amountOrId, metadata),
msg.value
);
emit SentTransferRemote(_destination, _recipient, _amount);
emit SentTransferRemote(_destination, _recipient, _amountOrId);
}
function _transferFromSender(uint256 _amount) internal virtual;
/**
* @dev Should transfer `_amountOrId` of tokens from `msg.sender` to this token router.
* @dev Called by `transferRemote` before message dispatch.
* @dev Optionally returns `metadata` associated with the transfer to be passed in message.
*/
function _transferFromSender(uint256 _amountOrId)
internal
virtual
returns (bytes memory metadata);
/**
* @dev Mints tokens to recipient when router receives transfer message.
@ -76,9 +82,19 @@ abstract contract TokenRouter is Router {
) internal override {
bytes32 recipient = _message.recipient();
uint256 amount = _message.amount();
_transferTo(recipient.bytes32ToAddress(), amount);
bytes calldata metadata = _message.metadata();
_transferTo(recipient.bytes32ToAddress(), amount, metadata);
emit ReceivedTransferRemote(_origin, recipient, amount);
}
function _transferTo(address _recipient, uint256 _amount) internal virtual;
/**
* @dev Should transfer `_amountOrId` of tokens from this token router to `_recipient`.
* @dev Called by `handle` after message decoding.
* @dev Optionally handles `metadata` associated with transfer passed in message.
*/
function _transferTo(
address _recipient,
uint256 _amountOrId,
bytes calldata metadata
) internal virtual;
}

@ -13,4 +13,8 @@ contract ERC721Test is ERC721 {
_mint(msg.sender, i);
}
}
function _baseURI() internal pure override returns (string memory) {
return "TEST-BASE-URI";
}
}

@ -2,22 +2,38 @@ import { ethers } from 'ethers';
import { RouterConfig } from '@hyperlane-xyz/sdk';
export enum TokenType {
synthetic,
syntheticUri,
collateral,
collateralUri,
}
export type SyntheticConfig = {
type: "SYNTHETIC";
type: TokenType.synthetic | TokenType.syntheticUri;
name: string;
symbol: string;
totalSupply: ethers.BigNumberish;
};
export type CollateralConfig = {
type: "COLLATERAL";
type: TokenType.collateral | TokenType.collateralUri;
token: string;
}
};
export type TokenConfig = SyntheticConfig | CollateralConfig;
export const isCollateralConfig = (config: RouterConfig & TokenConfig): config is RouterConfig & CollateralConfig => {
return config.type === "COLLATERAL";
}
export const isCollateralConfig = (
config: RouterConfig & TokenConfig,
): config is RouterConfig & CollateralConfig => {
return (
config.type === TokenType.collateral ||
config.type === TokenType.collateralUri
);
};
export const isUriConfig = (config: RouterConfig & TokenConfig) =>
config.type === TokenType.syntheticUri ||
config.type === TokenType.collateralUri;
export type HypERC20Config = RouterConfig & TokenConfig;
export type HypERC20CollateralConfig = RouterConfig & CollateralConfig;

@ -5,9 +5,10 @@ import {
HypERC20Collateral,
HypERC721,
HypERC721Collateral,
HypERC721URICollateral,
} from './types';
export type HypERC20Contracts = RouterContracts<HypERC20 | HypERC20Collateral>;
export type HypERC721Contracts = RouterContracts<
HypERC721 | HypERC721Collateral
HypERC721 | HypERC721Collateral | HypERC721URICollateral
>;

@ -11,12 +11,13 @@ import {
HypERC721Config,
isCollateralConfig,
HypERC721CollateralConfig,
isUriConfig,
} from './config';
import {
HypERC20Contracts,
HypERC721Contracts,
} from './contracts';
import { HypERC20Collateral__factory, HypERC20__factory, HypERC721Collateral__factory, HypERC721__factory } from './types';
import { HypERC20Collateral__factory, HypERC20__factory, HypERC721Collateral__factory, HypERC721URICollateral__factory, HypERC721URIStorage__factory, HypERC721__factory } from './types';
export class HypERC20Deployer<
Chain extends ChainName // inferred from configured chains passed to constructor
@ -85,8 +86,8 @@ export class HypERC721Deployer<
if (isCollateralConfig(config)) {
const router = await this.deployContractFromFactory(
chain,
new HypERC721Collateral__factory(),
'HypERC721Collateral',
isUriConfig(config) ? new HypERC721URICollateral__factory() : new HypERC721Collateral__factory(),
`HypERC721${isUriConfig(config) ? 'URI' : ''}Collateral`,
[config.token],
);
await connection.handleTx(
@ -100,8 +101,8 @@ export class HypERC721Deployer<
} else {
const router = await this.deployContractFromFactory(
chain,
new HypERC721__factory(),
'HypERC721',
isUriConfig(config) ? new HypERC721URIStorage__factory() : new HypERC721__factory(),
`HypERC721${isUriConfig(config) ? 'URIStorage' : ''}`,
[],
);
await connection.handleTx(router.initialize(

@ -18,6 +18,7 @@ import {
HypERC20CollateralConfig,
HypERC20Config,
SyntheticConfig,
TokenType,
} from '../src/config';
import { HypERC20Contracts } from '../src/contracts';
import { HypERC20Deployer } from '../src/deploy';
@ -38,7 +39,7 @@ const amount = 10;
const testInterchainGasPayment = 123456789;
const tokenConfig: SyntheticConfig = {
type: 'SYNTHETIC',
type: TokenType.synthetic,
name: 'HypERC20',
symbol: 'HYP',
totalSupply,
@ -80,7 +81,7 @@ for (const withCollateral of [true, false]) {
);
configWithTokenInfo.test1 = {
...configWithTokenInfo.test1,
type: 'COLLATERAL',
type: TokenType.collateral,
token: erc20.address,
};
}
@ -123,18 +124,16 @@ for (const withCollateral of [true, false]) {
await expectBalance(remote, owner, totalSupply);
});
it('should allow for local transfers', async () => {
// do not test underlying ERC20 collateral functionality
if (withCollateral) {
return;
}
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);
});
// do not test underlying ERC20 collateral functionality
if (!withCollateral) {
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('should allow for remote transfers', async () => {
await local.transferRemote(

@ -17,7 +17,8 @@ import { utils } from '@hyperlane-xyz/utils';
import {
HypERC721CollateralConfig,
HypERC721Config,
TokenConfig,
SyntheticConfig,
TokenType,
} from '../src/config';
import { HypERC721Contracts } from '../src/contracts';
import { HypERC721Deployer } from '../src/deploy';
@ -27,6 +28,8 @@ import {
ERC721__factory,
HypERC721,
HypERC721Collateral,
HypERC721URICollateral,
HypERC721URIStorage,
} from '../src/types';
const localChain = 'test1';
@ -40,216 +43,238 @@ const tokenId3 = 30;
const tokenId4 = 40;
const testInterchainGasPayment = 123456789;
const tokenConfig: TokenConfig = {
type: 'SYNTHETIC',
name: 'HypERC721',
symbol: 'HYP',
totalSupply,
};
for (const withCollateral of [true, false]) {
for (const withUri of [true, false]) {
const tokenConfig: SyntheticConfig = {
type: withUri ? TokenType.syntheticUri : TokenType.synthetic,
name: 'HypERC721',
symbol: 'HYP',
totalSupply,
};
const configMap = {
test1: {
...tokenConfig,
totalSupply,
},
test2: {
...tokenConfig,
totalSupply: 0,
},
test3: {
...tokenConfig,
totalSupply: 0,
},
};
const configMap = {
test1: {
...tokenConfig,
totalSupply,
},
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<TestChainNames>;
let contracts: Record<TestChainNames, HypERC721Contracts>;
let local: HypERC721 | HypERC721Collateral | HypERC721URICollateral;
let remote: HypERC721 | HypERC721Collateral | HypERC721URIStorage;
for (const withCollateral of [true, false]) {
describe(`HypERC721${withCollateral ? 'Collateral' : ''}`, async () => {
let owner: SignerWithAddress;
let recipient: SignerWithAddress;
let core: TestCoreApp;
let deployer: HypERC721Deployer<TestChainNames>;
let contracts: Record<TestChainNames, HypERC721Contracts>;
let local: HypERC721 | HypERC721Collateral;
let remote: HypERC721 | HypERC721Collateral;
beforeEach(async () => {
[owner, recipient] = await ethers.getSigners();
const multiProvider = getTestMultiProvider(owner);
beforeEach(async () => {
[owner, recipient] = await ethers.getSigners();
const multiProvider = getTestMultiProvider(owner);
const coreDeployer = new TestCoreDeployer(multiProvider);
const coreContractsMaps = await coreDeployer.deploy();
core = new TestCoreApp(coreContractsMaps, multiProvider);
const coreConfig = core.getConnectionClientConfigMap();
const configWithTokenInfo: ChainMap<
TestChainNames,
HypERC721Config | HypERC721CollateralConfig
> = objMap(coreConfig, (key) => ({
...coreConfig[key],
...configMap[key],
owner: owner.address,
}));
const coreDeployer = new TestCoreDeployer(multiProvider);
const coreContractsMaps = await coreDeployer.deploy();
core = new TestCoreApp(coreContractsMaps, multiProvider);
const coreConfig = core.getConnectionClientConfigMap();
const configWithTokenInfo: ChainMap<
TestChainNames,
HypERC721Config | HypERC721CollateralConfig
> = 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 = {
...configWithTokenInfo.test1,
type: withUri ? TokenType.collateralUri : TokenType.collateral,
token: erc721.address,
};
}
let erc721: ERC721 | undefined;
if (withCollateral) {
erc721 = await new ERC721Test__factory(owner).deploy(
tokenConfig.name,
tokenConfig.symbol,
tokenConfig.totalSupply,
deployer = new HypERC721Deployer(
multiProvider,
configWithTokenInfo,
core,
);
configWithTokenInfo.test1 = {
...configWithTokenInfo.test1,
type: 'COLLATERAL',
token: erc721.address,
};
}
deployer = new HypERC721Deployer(
multiProvider,
configWithTokenInfo,
core,
);
contracts = await deployer.deploy();
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);
}
contracts = await deployer.deploy();
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);
}
remote = contracts[remoteChain].router;
});
remote = contracts[remoteChain].router;
});
it('should not be initializable again', async () => {
const initializeTx = withCollateral
? (local as HypERC721Collateral).initialize(
ethers.constants.AddressZero,
ethers.constants.AddressZero,
ethers.constants.AddressZero,
)
: (local as HypERC721).initialize(
ethers.constants.AddressZero,
ethers.constants.AddressZero,
ethers.constants.AddressZero,
0,
'',
'',
);
await expect(initializeTx).to.be.revertedWith(
'Initializable: contract is already initialized',
);
});
it('should not be initializable again', async () => {
const initializeTx = withCollateral
? (local as HypERC721Collateral).initialize(
ethers.constants.AddressZero,
ethers.constants.AddressZero,
ethers.constants.AddressZero,
)
: (local as HypERC721).initialize(
ethers.constants.AddressZero,
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);
});
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);
});
it('should allow for local transfers', async () => {
// do not test underlying ERC721 collateral functionality
if (withCollateral) {
return;
}
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(
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,
utils.addressToBytes32(recipient.address),
invalidTokenId,
),
).to.be.revertedWith('ERC721: invalid token ID');
}
await expect(
local.transferRemote(
});
it('should allow for remote transfers', async () => {
await local.transferRemote(
remoteDomain,
utils.addressToBytes32(recipient.address),
invalidTokenId,
),
).to.be.revertedWith('ERC721: invalid token ID');
});
tokenId2,
);
it('should allow for remote transfers', async () => {
await local.transferRemote(
remoteDomain,
utils.addressToBytes32(recipient.address),
tokenId2,
);
await expectBalance(local, recipient, 0);
await expectBalance(local, owner, totalSupply - 1);
await expectBalance(remote, recipient, 0);
await expectBalance(remote, owner, 0);
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 core.processMessages();
await expectBalance(local, recipient, 0);
await expectBalance(local, owner, totalSupply - 1);
await expectBalance(remote, recipient, 1);
await expectBalance(remote, owner, 0);
});
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('');
it('should prevent remote transfer of unowned id', async () => {
const revertReason = withCollateral
? 'ERC721: transfer from incorrect owner'
: '!owner';
await expect(
local
.connect(recipient)
.transferRemote(
await local.transferRemote(
remoteDomain,
utils.addressToBytes32(recipient.address),
tokenId2,
),
).to.be.revertedWith(revertReason);
});
);
it('allows interchain gas payment for remote transfers', async () => {
const interchainGasPaymaster =
core.contractsMap[localChain].interchainGasPaymaster.contract;
await expect(
local.transferRemote(
remoteDomain,
utils.addressToBytes32(recipient.address),
tokenId3,
{
value: testInterchainGasPayment,
},
),
).to.emit(interchainGasPaymaster, 'GasPayment');
});
await expect(remoteUri.tokenURI(tokenId2)).to.be.revertedWith('');
it('should emit TransferRemote events', async () => {
expect(
await local.transferRemote(
remoteDomain,
utils.addressToBytes32(recipient.address),
tokenId4,
),
)
.to.emit(local, 'SentTransferRemote')
.withArgs(remoteDomain, recipient.address, tokenId4);
expect(await core.processMessages())
.to.emit(local, 'ReceivedTransferRemote')
.withArgs(localDomain, recipient.address, tokenId4);
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,
utils.addressToBytes32(recipient.address),
tokenId2,
),
).to.be.revertedWith(revertReason);
});
it('allows interchain gas payment for remote transfers', async () => {
const interchainGasPaymaster =
core.contractsMap[localChain].interchainGasPaymaster.contract;
await expect(
local.transferRemote(
remoteDomain,
utils.addressToBytes32(recipient.address),
tokenId3,
{
value: testInterchainGasPayment,
},
),
).to.emit(interchainGasPaymaster, 'GasPayment');
});
it('should emit TransferRemote events', async () => {
expect(
await local.transferRemote(
remoteDomain,
utils.addressToBytes32(recipient.address),
tokenId4,
),
)
.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 (

Loading…
Cancel
Save