Add TokenBridge deployment (#1215)

* Add TokenBridge deployment

* PR review

* Remove only

* Fix hardhat

* Fix ethers

* PR review

* PR review

* lint

* Undo merging into core addresses
pull/1228/head
Nam Chu Hoai 2 years ago committed by GitHub
parent 7f54dcbac3
commit fd54116155
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      solidity/contracts/middleware/token-bridge/adapters/CircleBridgeAdapter.sol
  2. 38
      solidity/contracts/mock/MockCircleBridge.sol
  3. 43
      solidity/contracts/mock/MockCircleMessageTransmitter.sol
  4. 46
      solidity/contracts/mock/MockTokenBridgeAdapter.sol
  5. 73
      solidity/test/TokenBridgeRouter.t.sol
  6. 10
      typescript/infra/config/environments/testnet2/middleware/token-bridge/addresses.json
  7. 30
      typescript/infra/config/environments/testnet2/middleware/token-bridge/verification.json
  8. 18
      typescript/infra/config/environments/testnet2/testrecipient/addresses.json
  9. 48
      typescript/infra/config/environments/testnet2/testrecipient/verification.json
  10. 32
      typescript/infra/config/environments/testnet2/token-bridge.ts
  11. 0
      typescript/infra/scripts/middleware/deploy-accounts.ts
  12. 0
      typescript/infra/scripts/middleware/deploy-queries.ts
  13. 48
      typescript/infra/scripts/middleware/deploy-token-bridge.ts
  14. 16
      typescript/infra/src/testrecipient/index.ts
  15. 7
      typescript/sdk/src/deploy/middleware/TokenBridgeApp.ts
  16. 174
      typescript/sdk/src/deploy/middleware/TokenBridgeRouterDeployer.ts
  17. 2
      typescript/sdk/src/deploy/router/HyperlaneRouterDeployer.ts
  18. 9
      typescript/sdk/src/index.ts
  19. 17
      typescript/sdk/src/middleware.ts
  20. 138
      typescript/sdk/src/middleware/tokenbridge.hardhat-test.ts

@ -121,8 +121,7 @@ contract CircleBridgeAdapter is ITokenBridgeAdapter, Router {
);
emit BridgedToken(_nonce);
return abi.encodePacked(_nonce, _tokenSymbol);
return abi.encode(_nonce, _tokenSymbol);
}
// Returns the token and amount sent

@ -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);
}
}

@ -3,9 +3,11 @@ pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import {TokenBridgeRouter} from "../contracts/middleware/token-bridge/TokenBridgeRouter.sol";
import {CircleBridgeAdapter} from "../contracts/middleware/token-bridge/adapters/CircleBridgeAdapter.sol";
import {MockToken} from "../contracts/mock/MockToken.sol";
import {TestTokenRecipient} from "../contracts/test/TestTokenRecipient.sol";
import {MockTokenBridgeAdapter} from "../contracts/mock/MockTokenBridgeAdapter.sol";
import {MockCircleMessageTransmitter} from "../contracts/mock/MockCircleMessageTransmitter.sol";
import {MockCircleBridge} from "../contracts/mock/MockCircleBridge.sol";
import {MockHyperlaneEnvironment} from "./MockHyperlaneEnvironment.sol";
import {TypeCasts} from "../contracts/libs/TypeCasts.sol";
@ -16,8 +18,11 @@ contract TokenBridgeRouterTest is Test {
TokenBridgeRouter originTokenBridgeRouter;
TokenBridgeRouter destinationTokenBridgeRouter;
// Origin bridge adapter
MockTokenBridgeAdapter bridgeAdapter;
MockCircleMessageTransmitter messageTransmitter;
MockCircleBridge circleBridge;
CircleBridgeAdapter originBridgeAdapter;
CircleBridgeAdapter destinationBridgeAdapter;
string bridge = "FooBridge";
uint32 originDomain = 123;
@ -32,7 +37,12 @@ contract TokenBridgeRouterTest is Test {
function setUp() public {
token = new MockToken();
bridgeAdapter = new MockTokenBridgeAdapter(token);
circleBridge = new MockCircleBridge(token);
messageTransmitter = new MockCircleMessageTransmitter(token);
originBridgeAdapter = new CircleBridgeAdapter();
destinationBridgeAdapter = new CircleBridgeAdapter();
recipient = new TestTokenRecipient();
originTokenBridgeRouter = new TokenBridgeRouter();
@ -64,14 +74,40 @@ contract TokenBridgeRouterTest is Test {
TypeCasts.addressToBytes32(address(originTokenBridgeRouter))
);
originBridgeAdapter.initialize(
address(this),
address(circleBridge),
address(messageTransmitter),
address(originTokenBridgeRouter)
);
destinationBridgeAdapter.initialize(
address(this),
address(circleBridge),
address(messageTransmitter),
address(destinationTokenBridgeRouter)
);
originBridgeAdapter.addToken(address(token), "USDC");
destinationBridgeAdapter.addToken(address(token), "USDC");
originBridgeAdapter.enrollRemoteRouter(
destinationDomain,
TypeCasts.addressToBytes32(address(destinationBridgeAdapter))
);
destinationBridgeAdapter.enrollRemoteRouter(
destinationDomain,
TypeCasts.addressToBytes32(address(originBridgeAdapter))
);
originTokenBridgeRouter.setTokenBridgeAdapter(
bridge,
address(bridgeAdapter)
address(originBridgeAdapter)
);
destinationTokenBridgeRouter.setTokenBridgeAdapter(
bridge,
address(bridgeAdapter)
address(destinationBridgeAdapter)
);
token.mint(address(this), amount);
@ -81,18 +117,18 @@ contract TokenBridgeRouterTest is Test {
// Expect the TokenBridgeAdapterSet event.
// Expect topic0 & data to match
vm.expectEmit(true, false, false, true);
emit TokenBridgeAdapterSet(bridge, address(bridgeAdapter));
emit TokenBridgeAdapterSet(bridge, address(originBridgeAdapter));
// Set the token bridge adapter
originTokenBridgeRouter.setTokenBridgeAdapter(
bridge,
address(bridgeAdapter)
address(originBridgeAdapter)
);
// Expect the bridge adapter to have been set
assertEq(
originTokenBridgeRouter.tokenBridgeAdapters(bridge),
address(bridgeAdapter)
address(originBridgeAdapter)
);
}
@ -136,9 +172,9 @@ contract TokenBridgeRouterTest is Test {
function testDispatchWithTokensCallsAdapter() public {
vm.expectCall(
address(bridgeAdapter),
address(originBridgeAdapter),
abi.encodeWithSelector(
bridgeAdapter.sendTokens.selector,
originBridgeAdapter.sendTokens.selector,
destinationDomain,
TypeCasts.addressToBytes32(address(recipient)),
address(token),
@ -167,7 +203,7 @@ contract TokenBridgeRouterTest is Test {
bridge
);
vm.expectRevert("Transfer has not been processed yet");
vm.expectRevert("Circle message not processed yet");
testEnvironment.processNextPendingMessage();
}
@ -182,7 +218,18 @@ contract TokenBridgeRouterTest is Test {
bridge
);
bridgeAdapter.process(bridgeAdapter.nonce());
bytes32 nonceId = messageTransmitter.hashSourceAndNonce(
destinationBridgeAdapter.hyperlaneDomainToCircleDomain(
originDomain
),
circleBridge.nextNonce()
);
messageTransmitter.process(
nonceId,
address(destinationBridgeAdapter),
amount
);
testEnvironment.processNextPendingMessage();
assertEq(recipient.lastData(), messageBody);
assertEq(token.balanceOf(address(recipient)), amount);

@ -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"
}
}

@ -5,6 +5,12 @@
"address": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE",
"isProxy": false,
"constructorArguments": ""
},
{
"name": "TestTokenRecipient",
"address": "0x36597C9C49F3c5887A86466398480ddB66aD0759",
"isProxy": false,
"constructorArguments": ""
}
],
"kovan": [
@ -13,6 +19,12 @@
"address": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE",
"isProxy": false,
"constructorArguments": ""
},
{
"name": "TestTokenRecipient",
"address": "0x36597C9C49F3c5887A86466398480ddB66aD0759",
"isProxy": false,
"constructorArguments": ""
}
],
"fuji": [
@ -21,6 +33,12 @@
"address": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE",
"isProxy": false,
"constructorArguments": ""
},
{
"name": "TestTokenRecipient",
"address": "0x36597C9C49F3c5887A86466398480ddB66aD0759",
"isProxy": false,
"constructorArguments": ""
}
],
"mumbai": [
@ -29,6 +47,12 @@
"address": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE",
"isProxy": false,
"constructorArguments": ""
},
{
"name": "TestTokenRecipient",
"address": "0x36597C9C49F3c5887A86466398480ddB66aD0759",
"isProxy": false,
"constructorArguments": ""
}
],
"bsctestnet": [
@ -37,6 +61,12 @@
"address": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE",
"isProxy": false,
"constructorArguments": ""
},
{
"name": "TestTokenRecipient",
"address": "0x36597C9C49F3c5887A86466398480ddB66aD0759",
"isProxy": false,
"constructorArguments": ""
}
],
"arbitrumrinkeby": [
@ -45,6 +75,12 @@
"address": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE",
"isProxy": false,
"constructorArguments": ""
},
{
"name": "TestTokenRecipient",
"address": "0x36597C9C49F3c5887A86466398480ddB66aD0759",
"isProxy": false,
"constructorArguments": ""
}
],
"optimismkovan": [
@ -53,6 +89,12 @@
"address": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE",
"isProxy": false,
"constructorArguments": ""
},
{
"name": "TestTokenRecipient",
"address": "0x36597C9C49F3c5887A86466398480ddB66aD0759",
"isProxy": false,
"constructorArguments": ""
}
],
"goerli": [
@ -61,6 +103,12 @@
"address": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE",
"isProxy": false,
"constructorArguments": ""
},
{
"name": "TestTokenRecipient",
"address": "0x36597C9C49F3c5887A86466398480ddB66aD0759",
"isProxy": false,
"constructorArguments": ""
}
],
"moonbasealpha": [

@ -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);

@ -1,4 +1,9 @@
import { TestRecipient, TestRecipient__factory } from '@hyperlane-xyz/core';
import {
TestRecipient,
TestRecipient__factory,
TestTokenRecipient,
TestTokenRecipient__factory,
} from '@hyperlane-xyz/core';
import {
ChainName,
HyperlaneDeployer,
@ -7,10 +12,12 @@ import {
export const factories = {
TestRecipient: new TestRecipient__factory(),
TestTokenRecipient: new TestTokenRecipient__factory(),
};
type Contracts = {
TestRecipient: TestRecipient;
TestTokenRecipient: TestTokenRecipient;
};
export class TestRecipientDeployer<
@ -30,8 +37,15 @@ export class TestRecipientDeployer<
[],
{ create2Salt: 'testtest32' },
);
const TestTokenRecipient = await this.deployContract(
chain,
'TestTokenRecipient',
[],
{ create2Salt: 'TestTokenRecipient' },
);
return {
TestRecipient,
TestTokenRecipient,
};
}
}

@ -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;
}
}

@ -68,7 +68,7 @@ export abstract class HyperlaneRouterDeployer<
}
async enrollRemoteRouters(
contractsMap: ChainMap<Chain, Contracts>,
contractsMap: ChainMap<Chain, RouterContracts>,
): Promise<void> {
this.logger(
`Enrolling deployed routers with each other (if not already)...`,

@ -115,9 +115,18 @@ export {
InterchainAccountDeployer,
InterchainQueryDeployer,
} from './deploy/middleware/deploy';
export {
TokenBridgeDeployer,
BridgeAdapterType,
BridgeAdapterConfig,
CircleBridgeAdapterConfig,
} from './deploy/middleware/TokenBridgeRouterDeployer';
export { TokenBridgeApp } from './deploy/middleware/TokenBridgeApp';
export {
interchainAccountFactories,
interchainQueryFactories,
tokenBridgeFactories,
} from './middleware';
export { RouterConfig } from './deploy/router/types';
export { getTestMultiProvider, getChainToOwnerMap } from './deploy/utils';

@ -1,8 +1,12 @@
import {
CircleBridgeAdapter,
CircleBridgeAdapter__factory,
InterchainAccountRouter,
InterchainAccountRouter__factory,
InterchainQueryRouter,
InterchainQueryRouter__factory,
TokenBridgeRouter,
TokenBridgeRouter__factory,
} from '@hyperlane-xyz/core';
import { RouterContracts, RouterFactories } from './router';
@ -24,3 +28,16 @@ export const interchainQueryFactories: InterchainQueryFactories = {
};
export type InterchainQueryContracts = RouterContracts<InterchainQueryRouter>;
export type TokenBridgeFactories = RouterFactories<TokenBridgeRouter> & {
circleBridgeAdapter: CircleBridgeAdapter__factory;
};
export const tokenBridgeFactories: TokenBridgeFactories = {
router: new TokenBridgeRouter__factory(),
circleBridgeAdapter: new CircleBridgeAdapter__factory(),
};
export type TokenBridgeContracts = RouterContracts<TokenBridgeRouter> & {
circleBridgeAdapter?: 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…
Cancel
Save