Add TokenBridge deployment (#1215)
* Add TokenBridge deployment * PR review * Remove only * Fix hardhat * Fix ethers * PR review * PR review * lint * Undo merging into core addressespull/1228/head
parent
7f54dcbac3
commit
fd54116155
@ -0,0 +1,38 @@ |
||||
// SPDX-License-Identifier: Apache-2.0 |
||||
pragma solidity ^0.8.13; |
||||
|
||||
import {ICircleBridge} from "../middleware/token-bridge/interfaces/circle/ICircleBridge.sol"; |
||||
import {MockToken} from "./MockToken.sol"; |
||||
|
||||
contract MockCircleBridge is ICircleBridge { |
||||
uint64 public nextNonce = 0; |
||||
MockToken token; |
||||
|
||||
constructor(MockToken _token) { |
||||
token = _token; |
||||
} |
||||
|
||||
function depositForBurn( |
||||
uint256 _amount, |
||||
uint32, |
||||
bytes32, |
||||
address _burnToken |
||||
) external returns (uint64 _nonce) { |
||||
nextNonce = nextNonce + 1; |
||||
_nonce = nextNonce; |
||||
require(address(token) == _burnToken); |
||||
token.transferFrom(msg.sender, address(this), _amount); |
||||
token.burn(_amount); |
||||
} |
||||
|
||||
function depositForBurnWithCaller( |
||||
uint256, |
||||
uint32, |
||||
bytes32, |
||||
address, |
||||
bytes32 |
||||
) external returns (uint64 _nonce) { |
||||
nextNonce = nextNonce + 1; |
||||
_nonce = nextNonce; |
||||
} |
||||
} |
@ -0,0 +1,43 @@ |
||||
// SPDX-License-Identifier: Apache-2.0 |
||||
pragma solidity ^0.8.13; |
||||
|
||||
import {ICircleMessageTransmitter} from "../middleware/token-bridge/interfaces/circle/ICircleMessageTransmitter.sol"; |
||||
import {MockToken} from "./MockToken.sol"; |
||||
|
||||
contract MockCircleMessageTransmitter is ICircleMessageTransmitter { |
||||
mapping(bytes32 => bool) processedNonces; |
||||
MockToken token; |
||||
|
||||
constructor(MockToken _token) { |
||||
token = _token; |
||||
} |
||||
|
||||
function receiveMessage(bytes memory, bytes calldata) |
||||
external |
||||
pure |
||||
returns (bool success) |
||||
{ |
||||
success = true; |
||||
} |
||||
|
||||
function hashSourceAndNonce(uint32 _source, uint256 _nonce) |
||||
public |
||||
pure |
||||
returns (bytes32) |
||||
{ |
||||
return keccak256(abi.encodePacked(_source, _nonce)); |
||||
} |
||||
|
||||
function process( |
||||
bytes32 _nonceId, |
||||
address _recipient, |
||||
uint256 _amount |
||||
) public { |
||||
processedNonces[_nonceId] = true; |
||||
token.mint(_recipient, _amount); |
||||
} |
||||
|
||||
function usedNonces(bytes32 _nonceId) external view returns (bool) { |
||||
return processedNonces[_nonceId]; |
||||
} |
||||
} |
@ -1,46 +0,0 @@ |
||||
// SPDX-License-Identifier: Apache-2.0 |
||||
pragma solidity ^0.8.13; |
||||
|
||||
import {ITokenBridgeAdapter} from "../middleware/token-bridge/interfaces/ITokenBridgeAdapter.sol"; |
||||
import {MockToken} from "./MockToken.sol"; |
||||
|
||||
contract MockTokenBridgeAdapter is ITokenBridgeAdapter { |
||||
uint256 public nonce = 0; |
||||
MockToken token; |
||||
|
||||
mapping(uint256 => bool) public isProcessed; |
||||
|
||||
constructor(MockToken _token) { |
||||
token = _token; |
||||
} |
||||
|
||||
function sendTokens( |
||||
uint32, |
||||
bytes32, |
||||
address _token, |
||||
uint256 _amount |
||||
) external override returns (bytes memory _adapterData) { |
||||
require(_token == address(token), "cant bridge this token"); |
||||
token.burn(_amount); |
||||
nonce = nonce + 1; |
||||
return abi.encode(nonce); |
||||
} |
||||
|
||||
function process(uint256 _nonce) public { |
||||
isProcessed[_nonce] = true; |
||||
} |
||||
|
||||
function receiveTokens( |
||||
uint32 _originDomain, // Hyperlane domain |
||||
address _recipientAddress, |
||||
uint256 _amount, |
||||
bytes calldata _adapterData // The adapter data from the message |
||||
) external override returns (address, uint256) { |
||||
_originDomain; |
||||
uint256 _nonce = abi.decode(_adapterData, (uint256)); |
||||
// Check if the transfer was processed first |
||||
require(isProcessed[_nonce], "Transfer has not been processed yet"); |
||||
token.mint(_recipientAddress, _amount); |
||||
return (address(0), 0); |
||||
} |
||||
} |
@ -0,0 +1,10 @@ |
||||
{ |
||||
"goerli": { |
||||
"circleBridgeAdapter": "0xc262a656c99B3a2f1B196dc5BeDa8f4f80D4a878", |
||||
"router": "0x952228cA63f85130534981844050c82b89f373E7" |
||||
}, |
||||
"fuji": { |
||||
"circleBridgeAdapter": "0xc262a656c99B3a2f1B196dc5BeDa8f4f80D4a878", |
||||
"router": "0x952228cA63f85130534981844050c82b89f373E7" |
||||
} |
||||
} |
@ -0,0 +1,30 @@ |
||||
{ |
||||
"goerli": [ |
||||
{ |
||||
"name": "TokenBridgeRouter", |
||||
"address": "0x952228cA63f85130534981844050c82b89f373E7", |
||||
"isProxy": false, |
||||
"constructorArguments": "" |
||||
}, |
||||
{ |
||||
"name": "CircleBridgeAdapter", |
||||
"address": "0xc262a656c99B3a2f1B196dc5BeDa8f4f80D4a878", |
||||
"isProxy": false, |
||||
"constructorArguments": "" |
||||
} |
||||
], |
||||
"fuji": [ |
||||
{ |
||||
"name": "TokenBridgeRouter", |
||||
"address": "0x952228cA63f85130534981844050c82b89f373E7", |
||||
"isProxy": false, |
||||
"constructorArguments": "" |
||||
}, |
||||
{ |
||||
"name": "CircleBridgeAdapter", |
||||
"address": "0xc262a656c99B3a2f1B196dc5BeDa8f4f80D4a878", |
||||
"isProxy": false, |
||||
"constructorArguments": "" |
||||
} |
||||
] |
||||
} |
@ -1,20 +1,26 @@ |
||||
{ |
||||
"alfajores": { |
||||
"TestRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE" |
||||
"TestRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE", |
||||
"TestTokenRecipient": "0x36597C9C49F3c5887A86466398480ddB66aD0759" |
||||
}, |
||||
"fuji": { |
||||
"TestRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE" |
||||
"TestRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE", |
||||
"TestTokenRecipient": "0x36597C9C49F3c5887A86466398480ddB66aD0759" |
||||
}, |
||||
"mumbai": { |
||||
"TestRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE" |
||||
"TestRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE", |
||||
"TestTokenRecipient": "0x36597C9C49F3c5887A86466398480ddB66aD0759" |
||||
}, |
||||
"bsctestnet": { |
||||
"TestRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE" |
||||
"TestRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE", |
||||
"TestTokenRecipient": "0x36597C9C49F3c5887A86466398480ddB66aD0759" |
||||
}, |
||||
"goerli": { |
||||
"TestRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE" |
||||
"TestRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE", |
||||
"TestTokenRecipient": "0x36597C9C49F3c5887A86466398480ddB66aD0759" |
||||
}, |
||||
"moonbasealpha": { |
||||
"TestRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE" |
||||
"TestRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE", |
||||
"TestTokenRecipient": "0x36597C9C49F3c5887A86466398480ddB66aD0759" |
||||
} |
||||
} |
||||
|
@ -0,0 +1,32 @@ |
||||
import { |
||||
BridgeAdapterType, |
||||
ChainMap, |
||||
Chains, |
||||
CircleBridgeAdapterConfig, |
||||
chainMetadata, |
||||
} from '@hyperlane-xyz/sdk'; |
||||
|
||||
const circleDomainMapping = [ |
||||
{ hyperlaneDomain: chainMetadata[Chains.goerli].id, circleDomain: 0 }, |
||||
{ hyperlaneDomain: chainMetadata[Chains.fuji].id, circleDomain: 1 }, |
||||
]; |
||||
|
||||
export const circleBridgeAdapterConfig: ChainMap< |
||||
any, |
||||
CircleBridgeAdapterConfig |
||||
> = { |
||||
[Chains.goerli]: { |
||||
type: BridgeAdapterType.Circle, |
||||
circleBridgeAddress: '0xdabec94b97f7b5fca28f050cc8eeac2dc9920476', |
||||
messageTransmitterAddress: '0x40a61d3d2afcf5a5d31fcdf269e575fb99dd87f7', |
||||
usdcAddress: '0x07865c6e87b9f70255377e024ace6630c1eaa37f', |
||||
circleDomainMapping, |
||||
}, |
||||
[Chains.fuji]: { |
||||
type: BridgeAdapterType.Circle, |
||||
circleBridgeAddress: '0x0fc1103927af27af808d03135214718bcedbe9ad', |
||||
messageTransmitterAddress: '0x52fffb3ee8fa7838e9858a2d5e454007b9027c3c', |
||||
usdcAddress: '0x5425890298aed601595a70ab815c96711a31bc65', |
||||
circleDomainMapping, |
||||
}, |
||||
}; |
@ -0,0 +1,48 @@ |
||||
import path from 'path'; |
||||
|
||||
import { |
||||
HyperlaneCore, |
||||
TokenBridgeDeployer, |
||||
objMap, |
||||
tokenBridgeFactories, |
||||
} from '@hyperlane-xyz/sdk'; |
||||
|
||||
import { circleBridgeAdapterConfig } from '../../config/environments/testnet2/token-bridge'; |
||||
import { deployWithArtifacts } from '../../src/deploy'; |
||||
import { getConfiguration } from '../helloworld/utils'; |
||||
import { |
||||
getCoreEnvironmentConfig, |
||||
getEnvironment, |
||||
getEnvironmentDirectory, |
||||
} from '../utils'; |
||||
|
||||
async function main() { |
||||
const environment = await getEnvironment(); |
||||
const coreConfig = getCoreEnvironmentConfig(environment); |
||||
const multiProvider = await coreConfig.getMultiProvider(); |
||||
const core = HyperlaneCore.fromEnvironment(environment, multiProvider as any); |
||||
|
||||
const dir = path.join( |
||||
getEnvironmentDirectory(environment), |
||||
'middleware/token-bridge', |
||||
); |
||||
|
||||
// config gcp deployer key as owner
|
||||
const ownerConfigMap = await getConfiguration(environment, multiProvider); |
||||
|
||||
const deployer = new TokenBridgeDeployer( |
||||
multiProvider, |
||||
objMap(circleBridgeAdapterConfig, (chain, conf) => ({ |
||||
bridgeAdapterConfigs: [conf], |
||||
...ownerConfigMap[chain], |
||||
})), |
||||
core, |
||||
'TokenBridgeDeploy2', |
||||
); |
||||
|
||||
await deployWithArtifacts(dir, tokenBridgeFactories, deployer); |
||||
} |
||||
|
||||
main() |
||||
.then(() => console.info('Deployment complete')) |
||||
.catch(console.error); |
@ -0,0 +1,7 @@ |
||||
import { HyperlaneApp } from '../../HyperlaneApp'; |
||||
import { TokenBridgeContracts } from '../../middleware'; |
||||
import { ChainName } from '../../types'; |
||||
|
||||
export class TokenBridgeApp< |
||||
Chain extends ChainName = ChainName, |
||||
> extends HyperlaneApp<TokenBridgeContracts, Chain> {} |
@ -0,0 +1,174 @@ |
||||
import { ethers } from 'ethers'; |
||||
|
||||
import { |
||||
CircleBridgeAdapter, |
||||
CircleBridgeAdapter__factory, |
||||
TokenBridgeRouter, |
||||
TokenBridgeRouter__factory, |
||||
} from '@hyperlane-xyz/core'; |
||||
import { objMap } from '@hyperlane-xyz/sdk/src/utils/objects'; |
||||
|
||||
import { HyperlaneCore } from '../../core/HyperlaneCore'; |
||||
import { |
||||
TokenBridgeContracts, |
||||
TokenBridgeFactories, |
||||
tokenBridgeFactories, |
||||
} from '../../middleware'; |
||||
import { MultiProvider } from '../../providers/MultiProvider'; |
||||
import { ChainMap, ChainName } from '../../types'; |
||||
import { HyperlaneRouterDeployer } from '../router/HyperlaneRouterDeployer'; |
||||
import { RouterConfig } from '../router/types'; |
||||
|
||||
export enum BridgeAdapterType { |
||||
Circle = 'Circle', |
||||
} |
||||
|
||||
export interface CircleBridgeAdapterConfig { |
||||
type: BridgeAdapterType.Circle; |
||||
circleBridgeAddress: string; |
||||
messageTransmitterAddress: string; |
||||
usdcAddress: string; |
||||
circleDomainMapping: { |
||||
hyperlaneDomain: number; |
||||
circleDomain: number; |
||||
}[]; |
||||
} |
||||
|
||||
export type BridgeAdapterConfig = CircleBridgeAdapterConfig; |
||||
|
||||
export type TokenBridgeConfig = RouterConfig & { |
||||
bridgeAdapterConfigs: BridgeAdapterConfig[]; |
||||
}; |
||||
|
||||
export class TokenBridgeDeployer< |
||||
Chain extends ChainName, |
||||
> extends HyperlaneRouterDeployer< |
||||
Chain, |
||||
TokenBridgeConfig, |
||||
TokenBridgeContracts, |
||||
TokenBridgeFactories |
||||
> { |
||||
constructor( |
||||
multiProvider: MultiProvider<Chain>, |
||||
configMap: ChainMap<Chain, TokenBridgeConfig>, |
||||
protected core: HyperlaneCore<Chain>, |
||||
protected create2salt = 'TokenBridgeDeployerSalt', |
||||
) { |
||||
super(multiProvider, configMap, tokenBridgeFactories, {}); |
||||
} |
||||
|
||||
async enrollRemoteRouters( |
||||
contractsMap: ChainMap<Chain, TokenBridgeContracts>, |
||||
): Promise<void> { |
||||
// Enroll the TokenBridgeRouter with each other
|
||||
await super.enrollRemoteRouters(contractsMap); |
||||
|
||||
// Enroll the circle adapters with each other
|
||||
await super.enrollRemoteRouters( |
||||
objMap(contractsMap, (_chain, contracts) => ({ |
||||
router: contracts.circleBridgeAdapter!, |
||||
})), |
||||
); |
||||
} |
||||
|
||||
// Custom contract deployment logic can go here
|
||||
// If no custom logic is needed, call deployContract for the router
|
||||
async deployContracts( |
||||
chain: Chain, |
||||
config: TokenBridgeConfig, |
||||
): Promise<TokenBridgeContracts> { |
||||
const initCalldata = |
||||
TokenBridgeRouter__factory.createInterface().encodeFunctionData( |
||||
'initialize', |
||||
[config.owner, config.connectionManager, config.interchainGasPaymaster], |
||||
); |
||||
const router = await this.deployContract(chain, 'router', [], { |
||||
create2Salt: this.create2salt, |
||||
initCalldata, |
||||
}); |
||||
|
||||
const bridgeAdapters: Partial<TokenBridgeContracts> = {}; |
||||
|
||||
for (const adapterConfig of config.bridgeAdapterConfigs) { |
||||
if (adapterConfig.type === BridgeAdapterType.Circle) { |
||||
bridgeAdapters.circleBridgeAdapter = |
||||
await this.deployCircleBridgeAdapter( |
||||
chain, |
||||
adapterConfig, |
||||
config.owner, |
||||
router, |
||||
); |
||||
} |
||||
} |
||||
|
||||
return { |
||||
...bridgeAdapters, |
||||
router, |
||||
}; |
||||
} |
||||
|
||||
async deployCircleBridgeAdapter( |
||||
chain: Chain, |
||||
adapterConfig: CircleBridgeAdapterConfig, |
||||
owner: string, |
||||
router: TokenBridgeRouter, |
||||
): Promise<CircleBridgeAdapter> { |
||||
const cc = this.multiProvider.getChainConnection(chain); |
||||
const initCalldata = |
||||
CircleBridgeAdapter__factory.createInterface().encodeFunctionData( |
||||
'initialize', |
||||
[ |
||||
owner, |
||||
adapterConfig.circleBridgeAddress, |
||||
adapterConfig.messageTransmitterAddress, |
||||
router.address, |
||||
], |
||||
); |
||||
const circleBridgeAdapter = await this.deployContract( |
||||
chain, |
||||
'circleBridgeAdapter', |
||||
[], |
||||
{ |
||||
create2Salt: this.create2salt, |
||||
initCalldata, |
||||
}, |
||||
); |
||||
|
||||
if ( |
||||
(await circleBridgeAdapter.tokenSymbolToAddress('USDC')) === |
||||
ethers.constants.AddressZero |
||||
) { |
||||
this.logger(`Set USDC token contract`); |
||||
await cc.handleTx( |
||||
circleBridgeAdapter.addToken(adapterConfig.usdcAddress, 'USDC'), |
||||
); |
||||
} |
||||
// Set domain mappings
|
||||
for (const { |
||||
circleDomain, |
||||
hyperlaneDomain, |
||||
} of adapterConfig.circleDomainMapping) { |
||||
const expectedCircleDomain = |
||||
await circleBridgeAdapter.hyperlaneDomainToCircleDomain( |
||||
hyperlaneDomain, |
||||
); |
||||
if (expectedCircleDomain === circleDomain) continue; |
||||
|
||||
this.logger( |
||||
`Set circle domain ${circleDomain} for hyperlane domain ${hyperlaneDomain}`, |
||||
); |
||||
await cc.handleTx( |
||||
circleBridgeAdapter.addDomain(hyperlaneDomain, circleDomain), |
||||
); |
||||
} |
||||
|
||||
this.logger('Set CircleTokenBridgeAdapter on Router'); |
||||
await cc.handleTx( |
||||
router.setTokenBridgeAdapter( |
||||
adapterConfig.type, |
||||
circleBridgeAdapter.address, |
||||
), |
||||
); |
||||
return circleBridgeAdapter; |
||||
} |
||||
} |
@ -0,0 +1,138 @@ |
||||
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; |
||||
import { expect } from 'chai'; |
||||
import { ethers } from 'hardhat'; |
||||
|
||||
import { |
||||
MockCircleBridge, |
||||
MockCircleBridge__factory, |
||||
MockCircleMessageTransmitter, |
||||
MockCircleMessageTransmitter__factory, |
||||
MockToken, |
||||
MockToken__factory, |
||||
TestTokenBridgeMessageRecipient__factory, |
||||
TokenBridgeRouter, |
||||
} from '@hyperlane-xyz/core'; |
||||
import { utils } from '@hyperlane-xyz/utils'; |
||||
|
||||
import { testChainConnectionConfigs } from '../consts/chainConnectionConfigs'; |
||||
import { TestCoreApp } from '../core/TestCoreApp'; |
||||
import { TestCoreDeployer } from '../core/TestCoreDeployer'; |
||||
import { TokenBridgeApp } from '../deploy/middleware/TokenBridgeApp'; |
||||
import { |
||||
BridgeAdapterType, |
||||
CircleBridgeAdapterConfig, |
||||
TokenBridgeConfig, |
||||
TokenBridgeDeployer, |
||||
} from '../deploy/middleware/TokenBridgeRouterDeployer'; |
||||
import { getChainToOwnerMap, getTestMultiProvider } from '../deploy/utils'; |
||||
import { ChainNameToDomainId } from '../domains'; |
||||
import { MultiProvider } from '../providers/MultiProvider'; |
||||
import { ChainMap, TestChainNames } from '../types'; |
||||
import { objMap } from '../utils/objects'; |
||||
|
||||
describe('TokenBridgeRouter', async () => { |
||||
const localChain = 'test1'; |
||||
const remoteChain = 'test2'; |
||||
const localDomain = ChainNameToDomainId[localChain]; |
||||
const remoteDomain = ChainNameToDomainId[remoteChain]; |
||||
|
||||
let signer: SignerWithAddress; |
||||
let local: TokenBridgeRouter; |
||||
let multiProvider: MultiProvider<TestChainNames>; |
||||
let coreApp: TestCoreApp; |
||||
|
||||
let tokenBridgeApp: TokenBridgeApp<TestChainNames>; |
||||
let config: ChainMap<TestChainNames, TokenBridgeConfig>; |
||||
let mockToken: MockToken; |
||||
let circleBridge: MockCircleBridge; |
||||
let messageTransmitter: MockCircleMessageTransmitter; |
||||
|
||||
before(async () => { |
||||
[signer] = await ethers.getSigners(); |
||||
|
||||
multiProvider = getTestMultiProvider(signer); |
||||
|
||||
const coreDeployer = new TestCoreDeployer(multiProvider); |
||||
const coreContractsMaps = await coreDeployer.deploy(); |
||||
coreApp = new TestCoreApp(coreContractsMaps, multiProvider); |
||||
|
||||
const mockTokenF = new MockToken__factory(signer); |
||||
mockToken = await mockTokenF.deploy(); |
||||
const circleBridgeF = new MockCircleBridge__factory(signer); |
||||
circleBridge = await circleBridgeF.deploy(mockToken.address); |
||||
const messageTransmitterF = new MockCircleMessageTransmitter__factory( |
||||
signer, |
||||
); |
||||
messageTransmitter = await messageTransmitterF.deploy(mockToken.address); |
||||
|
||||
config = coreApp.extendWithConnectionClientConfig( |
||||
objMap( |
||||
getChainToOwnerMap(testChainConnectionConfigs, signer.address), |
||||
(_chain, conf) => ({ |
||||
...conf, |
||||
bridgeAdapterConfigs: [ |
||||
{ |
||||
type: BridgeAdapterType.Circle, |
||||
circleBridgeAddress: circleBridge.address, |
||||
messageTransmitterAddress: messageTransmitter.address, |
||||
usdcAddress: mockToken.address, |
||||
circleDomainMapping: [ |
||||
{ |
||||
hyperlaneDomain: localDomain, |
||||
circleDomain: localDomain, |
||||
}, |
||||
{ |
||||
hyperlaneDomain: remoteDomain, |
||||
circleDomain: remoteDomain, |
||||
}, |
||||
], |
||||
} as CircleBridgeAdapterConfig, |
||||
], |
||||
}), |
||||
), |
||||
); |
||||
}); |
||||
|
||||
beforeEach(async () => { |
||||
const TokenBridge = new TokenBridgeDeployer(multiProvider, config, coreApp); |
||||
const contracts = await TokenBridge.deploy(); |
||||
|
||||
tokenBridgeApp = new TokenBridgeApp(contracts, multiProvider); |
||||
|
||||
local = tokenBridgeApp.getContracts(localChain).router; |
||||
}); |
||||
|
||||
it('can transfer tokens', async () => { |
||||
const recipientF = new TestTokenBridgeMessageRecipient__factory(signer); |
||||
const recipient = await recipientF.deploy(); |
||||
|
||||
const amount = 1000; |
||||
await mockToken.mint(signer.address, amount); |
||||
await mockToken.approve(local.address, amount); |
||||
await local.dispatchWithTokens( |
||||
remoteDomain, |
||||
utils.addressToBytes32(recipient.address), |
||||
'0x00', |
||||
mockToken.address, |
||||
amount, |
||||
BridgeAdapterType.Circle, |
||||
); |
||||
|
||||
const transferNonce = await circleBridge.nextNonce(); |
||||
const nonceId = await messageTransmitter.hashSourceAndNonce( |
||||
localDomain, |
||||
transferNonce, |
||||
); |
||||
|
||||
await messageTransmitter.process( |
||||
nonceId, |
||||
tokenBridgeApp.getContracts(remoteChain).circleBridgeAdapter!.address, |
||||
amount, |
||||
); |
||||
await coreApp.processMessages(); |
||||
|
||||
expect((await mockToken.balanceOf(recipient.address)).toNumber()).to.eql( |
||||
amount, |
||||
); |
||||
}); |
||||
}); |
Loading…
Reference in new issue