rs-2690: Fast warp routes (#2714)

### Description

This PR introduces a feature to warp routes such that LPs can fill
transactions on local chains and get a fee for doing this.

### Drive-by changes

No.

### Related issues

#2690 

### Backward compatibility

No

### Testing

Added Unit tests.

---------

Co-authored-by: Rohan Shrothrium <rohan.shrothrium@intellecteu.com>
Co-authored-by: Nam Chu Hoai <nambrot@googlemail.com>
pull/2758/head
Rohan Shrothrium 1 year ago committed by GitHub
parent d65c4e7e0a
commit 024058116e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 53
      typescript/token/contracts/FastHypERC20.sol
  2. 57
      typescript/token/contracts/FastHypERC20Collateral.sol
  3. 2
      typescript/token/contracts/HypERC20.sol
  4. 2
      typescript/token/contracts/HypERC20Collateral.sol
  5. 208
      typescript/token/contracts/libs/FastTokenRouter.sol
  6. 2
      typescript/token/contracts/libs/TokenRouter.sol
  7. 22
      typescript/token/src/config.ts
  8. 9
      typescript/token/src/contracts.ts
  9. 51
      typescript/token/src/deploy.ts
  10. 234
      typescript/token/test/erc20.test.ts

@ -0,0 +1,53 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.0;
import {HypERC20} from "./HypERC20.sol";
import {TokenRouter} from "./libs/TokenRouter.sol";
import {FastTokenRouter} from "./libs/FastTokenRouter.sol";
import {Message} from "./libs/Message.sol";
import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
/**
* @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.
*/
contract FastHypERC20 is FastTokenRouter, HypERC20 {
constructor(uint8 __decimals) HypERC20(__decimals) {}
/**
* @dev delegates transfer logic to `_transferTo`.
* @inheritdoc TokenRouter
*/
function _handle(
uint32 _origin,
bytes32 _sender,
bytes calldata _message
) internal virtual override(FastTokenRouter, TokenRouter) {
FastTokenRouter._handle(_origin, _sender, _message);
}
/**
* @dev Mints `_amount` of tokens to `_recipient`.
* @inheritdoc FastTokenRouter
*/
function _fastTransferTo(address _recipient, uint256 _amount)
internal
override
{
_mint(_recipient, _amount);
}
/**
* @dev Burns `_amount` of tokens from `_recipient`.
* @inheritdoc FastTokenRouter
*/
function _fastRecieveFrom(address _sender, uint256 _amount)
internal
override
{
_burn(_sender, _amount);
}
}

@ -0,0 +1,57 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.0;
import {HypERC20Collateral} from "./HypERC20Collateral.sol";
import {TokenRouter} from "./libs/TokenRouter.sol";
import {FastTokenRouter} from "./libs/FastTokenRouter.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/**
* @title Hyperlane ERC20 Token Collateral that wraps an existing ERC20 with remote transfer functionality.
* @author Abacus Works
*/
contract FastHypERC20Collateral is FastTokenRouter, HypERC20Collateral {
using SafeERC20 for IERC20;
/**
* @notice Constructor
* @param erc20 Address of the token to keep as collateral
*/
constructor(address erc20) HypERC20Collateral(erc20) {}
/**
* @dev delegates transfer logic to `_transferTo`.
* @inheritdoc FastTokenRouter
*/
function _handle(
uint32 _origin,
bytes32 _sender,
bytes calldata _message
) internal virtual override(FastTokenRouter, TokenRouter) {
FastTokenRouter._handle(_origin, _sender, _message);
}
/**
* @dev Transfers `_amount` of `wrappedToken` to `_recipient`.
* @inheritdoc FastTokenRouter
*/
function _fastTransferTo(address _recipient, uint256 _amount)
internal
override
{
wrappedToken.safeTransfer(_recipient, _amount);
}
/**
* @dev Transfers in `_amount` of `wrappedToken` from `_recipient`.
* @inheritdoc FastTokenRouter
*/
function _fastRecieveFrom(address _sender, uint256 _amount)
internal
override
{
wrappedToken.safeTransferFrom(_sender, address(this), _amount);
}
}

@ -68,7 +68,7 @@ contract HypERC20 is ERC20Upgradeable, TokenRouter {
address _recipient,
uint256 _amount,
bytes calldata // no metadata
) internal override {
) internal virtual override {
_mint(_recipient, _amount);
}
}

@ -64,7 +64,7 @@ contract HypERC20Collateral is TokenRouter {
address _recipient,
uint256 _amount,
bytes calldata // no metadata
) internal override {
) internal virtual override {
wrappedToken.safeTransfer(_recipient, _amount);
}
}

@ -0,0 +1,208 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.0;
import {Message} from "./Message.sol";
import {TokenRouter} from "./TokenRouter.sol";
import {TypeCasts} from "@hyperlane-xyz/core/contracts/libs/TypeCasts.sol";
/**
* @title Common FastTokenRouter functionality for ERC20 Tokens with remote transfer support.
* @author Abacus Works
*/
abstract contract FastTokenRouter is TokenRouter {
using TypeCasts for bytes32;
using Message for bytes;
uint256 public fastTransferId;
// maps `fastTransferId` to the filler address.
mapping(bytes32 => address) filledFastTransfers;
/**
* @dev delegates transfer logic to `_transferTo`.
* @inheritdoc TokenRouter
*/
function _handle(
uint32 _origin,
bytes32,
bytes calldata _message
) internal virtual override {
bytes32 recipient = _message.recipient();
uint256 amount = _message.amount();
bytes calldata metadata = _message.metadata();
_transferTo(recipient.bytes32ToAddress(), amount, _origin, metadata);
emit ReceivedTransferRemote(_origin, recipient, amount);
}
/**
* @dev Transfers `_amount` of token to `_recipient`/`fastFiller` who provided LP.
* @dev Called by `handle` after message decoding.
*/
function _transferTo(
address _recipient,
uint256 _amount,
uint32 _origin,
bytes calldata _metadata
) internal virtual {
address _tokenRecipient = _getTokenRecipient(
_recipient,
_amount,
_origin,
_metadata
);
_fastTransferTo(_tokenRecipient, _amount);
}
/**
* @dev allows an external user to full an unfilled fast transfer order.
* @param _recipient The recepient of the wrapped token on base chain.
* @param _amount The amount of wrapped tokens that is being bridged.
* @param _fastFee The fee the bridging entity will pay.
* @param _fastTransferId Id assigned on the remote chain to uniquely identify the transfer.
*/
function fillFastTransfer(
address _recipient,
uint256 _amount,
uint256 _fastFee,
uint32 _origin,
uint256 _fastTransferId
) external virtual {
bytes32 filledFastTransfersKey = _getFastTransfersKey(
_origin,
_fastTransferId,
_amount,
_fastFee,
_recipient
);
require(
filledFastTransfers[filledFastTransfersKey] == address(0),
"request already filled"
);
filledFastTransfers[filledFastTransfersKey] = msg.sender;
_fastRecieveFrom(msg.sender, _amount - _fastFee);
_fastTransferTo(_recipient, _amount - _fastFee);
}
/**
* @dev Transfers `_amountOrId` token to `_recipient` on `_destination` domain.
* @dev Delegates transfer logic to `_fastTransferFromSender` 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 _amountOrId The amount or identifier of tokens to be sent to the remote recipient.
* @return messageId The identifier of the dispatched message.
*/
function fastTransferRemote(
uint32 _destination,
bytes32 _recipient,
uint256 _amountOrId,
uint256 _fastFee
) public payable virtual returns (bytes32 messageId) {
uint256 _fastTransferId = fastTransferId;
fastTransferId = _fastTransferId + 1;
bytes memory metadata = _fastTransferFromSender(
_amountOrId,
_fastFee,
_fastTransferId + 1
);
messageId = _dispatchWithGas(
_destination,
Message.format(_recipient, _amountOrId, metadata),
msg.value, // interchain gas payment
msg.sender // refund address
);
emit SentTransferRemote(_destination, _recipient, _amountOrId);
}
/**
* @dev Burns `_amount` of token from `msg.sender` balance.
* @dev Pays `_fastFee` of tokens to LP on source chain.
* @dev Returns `fastFee` as bytes in the form of metadata.
*/
function _fastTransferFromSender(
uint256 _amount,
uint256 _fastFee,
uint256 _fastTransferId
) internal virtual returns (bytes memory) {
_fastRecieveFrom(msg.sender, _amount);
return abi.encode(_fastFee, _fastTransferId);
}
/**
* @dev returns an address that indicates who should recieve the bridged tokens.
* @dev if _fastFees was inlcuded and someone filled the order before the mailbox made the contract call, the filler gets the funds.
*/
function _getTokenRecipient(
address _recipient,
uint256 _amount,
uint32 _origin,
bytes calldata _metadata
) internal view returns (address) {
if (_metadata.length == 0) {
return _recipient;
}
// decode metadata to extract `_fastFee` and `_fastTransferId`.
(uint256 _fastFee, uint256 _fastTransferId) = abi.decode(
_metadata,
(uint256, uint256)
);
address _fillerAddress = filledFastTransfers[
_getFastTransfersKey(
_origin,
_fastTransferId,
_amount,
_fastFee,
_recipient
)
];
if (_fillerAddress != address(0)) {
return _fillerAddress;
}
return _recipient;
}
/**
* @dev generates the key for storing the filler address of fast transfers.
*/
function _getFastTransfersKey(
uint32 _origin,
uint256 _fastTransferId,
uint256 _amount,
uint256 _fastFee,
address _recipient
) internal pure returns (bytes32) {
return
keccak256(
abi.encodePacked(
_origin,
_fastTransferId,
_amount,
_fastFee,
_recipient
)
);
}
/**
* @dev Should transfer `_amount` of tokens to `_recipient`.
* @dev The implementation is delegated.
*/
function _fastTransferTo(address _recipient, uint256 _amount)
internal
virtual;
/**
* @dev Should collect `amount` of tokens from `_sender`.
* @dev The implementation is delegated.
*/
function _fastRecieveFrom(address _sender, uint256 _amount)
internal
virtual;
}

@ -102,7 +102,7 @@ abstract contract TokenRouter is GasRouter {
uint32 _origin,
bytes32,
bytes calldata _message
) internal override {
) internal virtual override {
bytes32 recipient = _message.recipient();
uint256 amount = _message.amount();
bytes calldata metadata = _message.metadata();

@ -4,8 +4,10 @@ import { GasRouterConfig } from '@hyperlane-xyz/sdk';
export enum TokenType {
synthetic = 'synthetic',
fastSynthetic = 'fastSynthetic',
syntheticUri = 'syntheticUri',
collateral = 'collateral',
fastCollateral = 'fastCollateral',
collateralUri = 'collateralUri',
native = 'native',
}
@ -30,11 +32,14 @@ export const isTokenMetadata = (metadata: any): metadata is TokenMetadata =>
export const isErc20Metadata = (metadata: any): metadata is ERC20Metadata =>
metadata.decimals && isTokenMetadata(metadata);
export type SyntheticConfig = {
type: TokenType.synthetic | TokenType.syntheticUri;
export type SyntheticConfig = TokenMetadata & {
type: TokenType.synthetic | TokenType.syntheticUri | TokenType.fastSynthetic;
} & TokenMetadata;
export type CollateralConfig = {
type: TokenType.collateral | TokenType.collateralUri;
type:
| TokenType.collateral
| TokenType.collateralUri
| TokenType.fastCollateral;
token: string;
} & Partial<ERC20Metadata>;
export type NativeConfig = {
@ -47,12 +52,15 @@ export const isCollateralConfig = (
config: TokenConfig,
): config is CollateralConfig =>
config.type === TokenType.collateral ||
config.type === TokenType.collateralUri;
config.type === TokenType.collateralUri ||
config.type === TokenType.fastCollateral;
export const isSyntheticConfig = (
config: TokenConfig,
): config is SyntheticConfig =>
config.type === TokenType.synthetic || config.type === TokenType.syntheticUri;
config.type === TokenType.synthetic ||
config.type === TokenType.syntheticUri ||
config.type === TokenType.fastSynthetic;
export const isNativeConfig = (config: TokenConfig): config is NativeConfig =>
config.type === TokenType.native;
@ -61,6 +69,10 @@ export const isUriConfig = (config: TokenConfig) =>
config.type === TokenType.syntheticUri ||
config.type === TokenType.collateralUri;
export const isFastConfig = (config: TokenConfig) =>
config.type === TokenType.fastSynthetic ||
config.type === TokenType.fastCollateral;
export type HypERC20Config = GasRouterConfig & SyntheticConfig & ERC20Metadata;
export type HypERC20CollateralConfig = GasRouterConfig &
CollateralConfig &

@ -1,4 +1,6 @@
import {
FastHypERC20Collateral__factory,
FastHypERC20__factory,
HypERC20Collateral__factory,
HypERC20__factory,
HypERC721Collateral__factory,
@ -8,7 +10,12 @@ import {
} from './types';
export type HypERC20Factories = {
router: HypERC20__factory | HypERC20Collateral__factory | HypNative__factory;
router:
| HypERC20__factory
| HypERC20Collateral__factory
| HypNative__factory
| FastHypERC20__factory
| FastHypERC20Collateral__factory;
};
export type HypERC721Factories = {
router:

@ -25,6 +25,7 @@ import {
TokenMetadata,
isCollateralConfig,
isErc20Metadata,
isFastConfig,
isNativeConfig,
isSyntheticConfig,
isTokenMetadata,
@ -34,6 +35,8 @@ import { HypERC20Factories, HypERC721Factories } from './contracts';
import {
ERC20__factory,
ERC721EnumerableUpgradeable__factory,
FastHypERC20Collateral__factory,
FastHypERC20__factory,
HypERC20,
HypERC20Collateral,
HypERC20Collateral__factory,
@ -75,11 +78,14 @@ export class HypERC20Deployer extends GasRouterDeployer<
static gasOverheadDefault(config: TokenConfig): number {
switch (config.type) {
case 'fastSynthetic':
return 64_000;
case 'synthetic':
return 64_000;
case 'native':
return 44_000;
case 'collateral':
case 'fastCollateral':
default:
return 68_000;
}
@ -124,12 +130,23 @@ export class HypERC20Deployer extends GasRouterDeployer<
chain: ChainName,
config: HypERC20CollateralConfig,
): Promise<HypERC20Collateral> {
const router = await this.deployContractFromFactory(
chain,
new HypERC20Collateral__factory(),
'HypERC20Collateral',
[config.token],
);
let router: HypERC20Collateral;
if (isFastConfig(config)) {
router = await this.deployContractFromFactory(
chain,
new FastHypERC20Collateral__factory(),
'FastHypERC20Collateral',
[config.token],
);
} else {
router = await this.deployContractFromFactory(
chain,
new HypERC20Collateral__factory(),
'HypERC20Collateral',
[config.token],
);
}
await this.multiProvider.handleTx(
chain,
router.initialize(config.mailbox, config.interchainGasPaymaster),
@ -168,12 +185,22 @@ export class HypERC20Deployer extends GasRouterDeployer<
chain: ChainName,
config: HypERC20Config,
): Promise<HypERC20> {
const router = await this.deployContractFromFactory(
chain,
new HypERC20__factory(),
'HypERC20',
[config.decimals],
);
let router: HypERC20;
if (isFastConfig(config)) {
router = await this.deployContractFromFactory(
chain,
new FastHypERC20__factory(),
'FastHypERC20Collateral',
[config.decimals],
);
} else {
router = await this.deployContractFromFactory(
chain,
new HypERC20__factory(),
'HypERC20',
[config.decimals],
);
}
await this.multiProvider.handleTx(
chain,
router.initialize(

@ -1,7 +1,8 @@
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import '@nomiclabs/hardhat-waffle';
import { expect } from 'chai';
import { BigNumber, BigNumberish } from 'ethers';
import { error } from 'console';
import { BigNumber, BigNumberish, utils } from 'ethers';
import { ethers } from 'hardhat';
import { InterchainGasPaymaster__factory } from '@hyperlane-xyz/core';
@ -24,6 +25,8 @@ import {
ERC20,
ERC20Test__factory,
ERC20__factory,
FastHypERC20,
FastHypERC20Collateral,
HypERC20,
HypERC20Collateral,
HypNative,
@ -35,6 +38,7 @@ let localDomain: number;
let remoteDomain: number;
const totalSupply = 3000;
const amount = 10;
const fastFee = 1;
const tokenMetadata = {
name: 'HypERC20',
@ -46,21 +50,24 @@ const tokenMetadata = {
for (const variant of [
TokenType.synthetic,
TokenType.collateral,
TokenType.fastSynthetic,
TokenType.fastCollateral,
TokenType.native,
]) {
describe(`HypERC20${variant}`, async () => {
let owner: SignerWithAddress;
let recipient: SignerWithAddress;
let fastFiller: SignerWithAddress;
let core: TestCoreApp;
let deployer: HypERC20Deployer;
let contracts: HyperlaneContractsMap<HypERC20Factories>;
let localTokenConfig: TokenConfig;
let local: HypERC20 | HypERC20Collateral | HypNative;
let remote: HypERC20;
let remote: HypERC20 | FastHypERC20;
let interchainGasPayment: BigNumber;
beforeEach(async () => {
[owner, recipient] = await ethers.getSigners();
[owner, recipient, fastFiller] = await ethers.getSigners();
const multiProvider = MultiProvider.createTestMultiProvider({
signer: owner,
});
@ -77,7 +84,10 @@ for (const variant of [
);
let erc20: ERC20 | undefined;
if (variant === TokenType.collateral) {
if (
variant === TokenType.collateral ||
variant === TokenType.fastCollateral
) {
erc20 = await new ERC20Test__factory(owner).deploy(
tokenMetadata.name,
tokenMetadata.symbol,
@ -92,7 +102,10 @@ for (const variant of [
localTokenConfig = {
type: variant,
};
} else if (variant === TokenType.synthetic) {
} else if (
variant === TokenType.synthetic ||
variant === TokenType.fastSynthetic
) {
localTokenConfig = { type: variant, ...tokenMetadata };
}
@ -100,7 +113,7 @@ for (const variant of [
...routerConfig[key],
...(key === localChain
? localTokenConfig
: { type: TokenType.synthetic }),
: { type: TokenType.fastSynthetic }),
owner: owner.address,
})) as ChainMap<TokenConfig & RouterConfig>;
@ -114,7 +127,10 @@ for (const variant of [
interchainGasPayment = interchainGasPayment.add(amount);
}
if (variant === TokenType.collateral) {
if (
variant === TokenType.collateral ||
variant === TokenType.fastCollateral
) {
await erc20!.approve(local.address, amount);
}
@ -123,7 +139,9 @@ for (const variant of [
it('should not be initializable again', async () => {
const initializeTx =
variant === TokenType.collateral || variant === TokenType.native
variant === TokenType.collateral ||
variant === TokenType.native ||
variant === TokenType.fastCollateral
? (local as HypERC20Collateral).initialize(
ethers.constants.AddressZero,
ethers.constants.AddressZero,
@ -140,7 +158,10 @@ for (const variant of [
);
});
if (variant === TokenType.synthetic) {
if (
variant === TokenType.synthetic ||
variant === TokenType.fastSynthetic
) {
it('should mint total supply to deployer', async () => {
await expectBalance(local, recipient, 0);
await expectBalance(local, owner, totalSupply);
@ -160,7 +181,10 @@ for (const variant of [
it('benchmark handle gas overhead', async () => {
const localRaw = local.connect(ethers.provider);
const mailboxAddress = core.contractsMap[localChain].mailbox.address;
if (variant === TokenType.collateral) {
if (
variant === TokenType.collateral ||
variant === TokenType.fastCollateral
) {
const tokenAddress = await (local as HypERC20Collateral).wrappedToken();
const token = ERC20__factory.connect(tokenAddress, owner);
await token.transfer(local.address, totalSupply);
@ -174,12 +198,31 @@ for (const variant of [
{ value: interchainGasPayment },
);
}
const message = `${addressToBytes32(recipient.address)}${BigNumber.from(
amount,
)
.toHexString()
.slice(2)
.padStart(64, '0')}`;
let message: string;
if (
variant == TokenType.fastCollateral ||
variant === TokenType.fastSynthetic
) {
const metadata: string = utils.defaultAbiCoder.encode(
['uint256', 'uint256'],
[0, 0],
);
message = `${addressToBytes32(recipient.address)}${BigNumber.from(
amount,
)
.toHexString()
.slice(2)
.padStart(64, '0')}${metadata.slice(2)}`;
} else {
message = `${addressToBytes32(recipient.address)}${BigNumber.from(
amount,
)
.toHexString()
.slice(2)
.padStart(64, '0')}`;
}
const handleGas = await localRaw.estimateGas.handle(
remoteDomain,
addressToBytes32(remote.address),
@ -227,6 +270,129 @@ for (const variant of [
await expectBalance(remote, owner, remoteOwner);
});
if (
variant === TokenType.fastCollateral ||
variant === TokenType.fastSynthetic
) {
it('should allow fast remote transfer from remote chain', async () => {
// First transfer tokens to the remote chain from the owner to owner.
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(owner.address),
amount,
{
value: interchainGasPayment,
},
);
let expectedLocal = localOwner.sub(amount);
await expectBalance(local, recipient, localRecipient);
await expectBalance(local, owner, expectedLocal);
await expectBalance(remote, recipient, remoteRecipient);
await expectBalance(remote, owner, remoteOwner);
await core.processMessages();
await expectBalance(local, recipient, localRecipient);
await expectBalance(local, owner, expectedLocal);
await expectBalance(remote, recipient, remoteRecipient);
await expectBalance(remote, owner, remoteOwner.add(amount));
// Transfer tokens back to the local chain from the remote chain, but now use the `fastTransferRemote` method.
const remoteInterchainGasPayment = await remote.quoteGasPayment(
localDomain,
);
await (remote as FastHypERC20).fastTransferRemote(
localDomain,
addressToBytes32(recipient.address),
amount,
fastFee,
{
value: remoteInterchainGasPayment,
},
);
await expectBalance(local, recipient, localRecipient);
await expectBalance(local, owner, expectedLocal);
await expectBalance(remote, recipient, remoteRecipient);
await expectBalance(remote, owner, remoteOwner);
// Transfer some wrapped tokens to the fast filler.
const token = await transferToFastFiller(
variant,
local,
owner,
fastFiller,
expectedLocal,
);
await token
.connect(fastFiller)
.approve(local.address, amount - fastFee);
// provide liquidity on the local chain and send amount to recipient
await (local as FastHypERC20Collateral)
.connect(fastFiller)
.fillFastTransfer(
recipient.address,
amount,
fastFee,
remoteDomain,
1,
);
await core.processMessages();
await expectBalance(
local,
recipient,
localRecipient.add(amount).sub(fastFee),
);
await expectBalance(local, owner, expectedLocal.sub(amount));
await expectBalance(remote, recipient, remoteRecipient);
await expectBalance(remote, owner, remoteOwner);
await expectBalance(token, fastFiller, amount + fastFee);
});
it('should credit full transfer amount to receiver if no one fills the transfer', async () => {
// First transfer tokens to the remote chain from the owner to owner.
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(owner.address),
amount,
{
value: interchainGasPayment,
},
);
let expectedLocal = localOwner.sub(amount);
await expectBalance(local, recipient, localRecipient);
await expectBalance(local, owner, expectedLocal);
await expectBalance(remote, recipient, remoteRecipient);
await expectBalance(remote, owner, remoteOwner);
await core.processMessages();
await expectBalance(local, recipient, localRecipient);
await expectBalance(local, owner, expectedLocal);
await expectBalance(remote, recipient, remoteRecipient);
await expectBalance(remote, owner, remoteOwner.add(amount));
});
}
it('allows interchain gas payment for remote transfers', async () => {
const interchainGasPaymaster = new InterchainGasPaymaster__factory()
.attach(await local.interchainGasPaymaster())
@ -245,8 +411,10 @@ for (const variant of [
const revertReason = (): string => {
switch (variant) {
case TokenType.synthetic:
case TokenType.fastSynthetic:
return 'ERC20: burn amount exceeds balance';
case TokenType.collateral:
case TokenType.fastCollateral:
return 'ERC20: insufficient allowance';
case TokenType.native:
return 'Native: amount exceeds msg.value';
@ -292,3 +460,37 @@ const expectBalance = async (
) => {
return expect(await token.balanceOf(signer.address)).to.eq(balance);
};
const transferToFastFiller = async (
variant: TokenType,
local: HypERC20 | HypERC20Collateral | HypNative,
owner: SignerWithAddress,
fastFiller: SignerWithAddress,
ownerBalance: BigNumber,
) => {
if (variant == TokenType.fastCollateral) {
// Transfer some wrapped tokens to the fast filler.
const tokenAddress = await (local as FastHypERC20Collateral).wrappedToken();
const token = ERC20__factory.connect(tokenAddress, owner);
await token.transfer(fastFiller.address, amount);
await expectBalance(local, owner, ownerBalance.sub(amount));
await expectBalance(local, fastFiller, amount);
return token;
} else if (variant == TokenType.fastSynthetic) {
// Transfer some wrapped tokens to the fast filler.
const tokenAddress = (local as FastHypERC20).address;
const token = ERC20__factory.connect(tokenAddress, owner);
await token.transfer(fastFiller.address, amount);
await expectBalance(local, owner, ownerBalance.sub(amount));
await expectBalance(local, fastFiller, amount);
return token;
}
throw error('unsupported type');
};

Loading…
Cancel
Save