Make helloworld and token submodules (#1475)
* Update CI to checkout submodules * Fix github actions syntax * Try removing locks * Reactivate submodulespull/1480/head
parent
6c40b382b8
commit
4a8c47c73c
@ -0,0 +1,8 @@ |
||||
[submodule "typescript/token"] |
||||
path = typescript/token |
||||
url = git@github.com:hyperlane-xyz/hyperlane-token.git |
||||
branch = main |
||||
[submodule "typescript/helloworld"] |
||||
path = typescript/helloworld |
||||
url = git@github.com:hyperlane-xyz/hyperlane-app-template.git |
||||
branch = main |
@ -0,0 +1 @@ |
||||
Subproject commit 646f1e7beb18a77e5ca2535dba130f75793aea0f |
@ -1,5 +0,0 @@ |
||||
{ |
||||
"rules": { |
||||
"no-console": ["off"] |
||||
} |
||||
} |
@ -1,11 +0,0 @@ |
||||
artifacts/ |
||||
cache/ |
||||
coverage/ |
||||
coverage.json |
||||
dist/ |
||||
node_modules/ |
||||
src/types/ |
||||
src/deploy/output |
||||
test/outputs/ |
||||
*.swp |
||||
.yarn/install-state.gz |
@ -1,2 +0,0 @@ |
||||
src/types |
||||
test/outputs |
@ -1,3 +0,0 @@ |
||||
module.exports = { |
||||
skipFiles: ['test'], |
||||
}; |
@ -1,8 +0,0 @@ |
||||
{ |
||||
"extends": "solhint:recommended", |
||||
"rules": { |
||||
"compiler-version": ["error", ">=0.6.0"], |
||||
"func-visibility": ["warn", {"ignoreConstructors":true}], |
||||
"not-rely-on-time": "off" |
||||
} |
||||
} |
@ -1,27 +0,0 @@ |
||||
# Hyperlane 'Hello World' App Template |
||||
|
||||
A basic Hyperlane application with a router contract that can dispatch messages. |
||||
|
||||
## Setup |
||||
|
||||
```sh |
||||
# Install dependencies |
||||
yarn |
||||
|
||||
# Build source and generate types |
||||
yarn build |
||||
``` |
||||
|
||||
## Test |
||||
|
||||
```sh |
||||
# Run all unit tests |
||||
yarn test |
||||
|
||||
# Lint check code |
||||
yarn lint |
||||
``` |
||||
|
||||
## Learn more |
||||
|
||||
For more information, see the [Hyperlane documentation](https://docs.hyperlane.xyz/hyperlane-docs/developers/getting-started). |
@ -1,120 +0,0 @@ |
||||
// SPDX-License-Identifier: Apache-2.0 |
||||
pragma solidity ^0.8.13; |
||||
|
||||
// ============ External Imports ============ |
||||
import {Router} from "@hyperlane-xyz/core/contracts/Router.sol"; |
||||
|
||||
/* |
||||
* @title The Hello World App |
||||
* @dev You can use this simple app as a starting point for your own application. |
||||
*/ |
||||
contract HelloWorld is Router { |
||||
// A counter of how many messages have been sent from this contract. |
||||
uint256 public sent; |
||||
// A counter of how many messages have been received by this contract. |
||||
uint256 public received; |
||||
|
||||
// Keyed by domain, a counter of how many messages that have been sent |
||||
// from this contract to the domain. |
||||
mapping(uint32 => uint256) public sentTo; |
||||
// Keyed by domain, a counter of how many messages that have been received |
||||
// by this contract from the domain. |
||||
mapping(uint32 => uint256) public receivedFrom; |
||||
|
||||
// Keyed by domain, a generous upper bound on the amount of gas to use in the |
||||
// handle function when a message is processed. Used for paying for gas. |
||||
mapping(uint32 => uint256) public handleGasAmounts; |
||||
|
||||
// ============ Events ============ |
||||
event SentHelloWorld( |
||||
uint32 indexed origin, |
||||
uint32 indexed destination, |
||||
string message |
||||
); |
||||
event ReceivedHelloWorld( |
||||
uint32 indexed origin, |
||||
uint32 indexed destination, |
||||
bytes32 sender, |
||||
string message |
||||
); |
||||
event HandleGasAmountSet( |
||||
uint32 indexed destination, |
||||
uint256 handleGasAmount |
||||
); |
||||
|
||||
constructor(address _mailbox, address _interchainGasPaymaster) { |
||||
// Transfer ownership of the contract to deployer |
||||
_transferOwnership(msg.sender); |
||||
// Set the addresses for the Mailbox and IGP |
||||
// Alternatively, this could be done later in an initialize method |
||||
_setMailbox(_mailbox); |
||||
_setInterchainGasPaymaster(_interchainGasPaymaster); |
||||
} |
||||
|
||||
// ============ External functions ============ |
||||
|
||||
/** |
||||
* @notice Sends a message to the _destinationDomain. Any msg.value is |
||||
* used as interchain gas payment. |
||||
* @param _destinationDomain The destination domain to send the message to. |
||||
* @param _message The message to send. |
||||
*/ |
||||
function sendHelloWorld(uint32 _destinationDomain, string calldata _message) |
||||
external |
||||
payable |
||||
{ |
||||
sent += 1; |
||||
sentTo[_destinationDomain] += 1; |
||||
_dispatchWithGas( |
||||
_destinationDomain, |
||||
bytes(_message), |
||||
handleGasAmounts[_destinationDomain], |
||||
msg.value, |
||||
msg.sender |
||||
); |
||||
emit SentHelloWorld( |
||||
mailbox.localDomain(), |
||||
_destinationDomain, |
||||
_message |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* @notice Sets the amount of gas the recipient's handle function uses on |
||||
* the destination domain, which is used when paying for gas. |
||||
* @dev Reverts if called by a non-owner. |
||||
* @param _destinationDomain The destination domain, |
||||
* @param _handleGasAmount The handle gas amount. |
||||
*/ |
||||
function setHandleGasAmount( |
||||
uint32 _destinationDomain, |
||||
uint256 _handleGasAmount |
||||
) external onlyOwner { |
||||
handleGasAmounts[_destinationDomain] = _handleGasAmount; |
||||
emit HandleGasAmountSet(_destinationDomain, _handleGasAmount); |
||||
} |
||||
|
||||
// ============ Internal functions ============ |
||||
|
||||
/** |
||||
* @notice Handles a message from a remote router. |
||||
* @dev Only called for messages sent from a remote router, as enforced by Router.sol. |
||||
* @param _origin The domain of the origin of the message. |
||||
* @param _sender The sender of the message. |
||||
* @param _message The message body. |
||||
*/ |
||||
function _handle( |
||||
uint32 _origin, |
||||
bytes32 _sender, |
||||
bytes calldata _message |
||||
) internal override { |
||||
received += 1; |
||||
receivedFrom[_origin] += 1; |
||||
emit ReceivedHelloWorld( |
||||
_origin, |
||||
mailbox.localDomain(), |
||||
_sender, |
||||
string(_message) |
||||
); |
||||
} |
||||
} |
@ -1,26 +0,0 @@ |
||||
import '@nomiclabs/hardhat-ethers'; |
||||
import '@nomiclabs/hardhat-waffle'; |
||||
import '@typechain/hardhat'; |
||||
import 'hardhat-gas-reporter'; |
||||
import 'solidity-coverage'; |
||||
|
||||
/** |
||||
* @type import('hardhat/config').HardhatUserConfig |
||||
*/ |
||||
module.exports = { |
||||
solidity: { |
||||
compilers: [ |
||||
{ |
||||
version: '0.8.16', |
||||
}, |
||||
], |
||||
}, |
||||
gasReporter: { |
||||
currency: 'USD', |
||||
}, |
||||
typechain: { |
||||
outDir: './src/types', |
||||
target: 'ethers-v5', |
||||
alwaysGenerateOverloads: false, // should overloads with full signatures like deposit(uint256) be generated always, even if there are no overloads?
|
||||
}, |
||||
}; |
@ -1,70 +0,0 @@ |
||||
{ |
||||
"name": "@hyperlane-xyz/helloworld", |
||||
"description": "A basic skeleton of an Hyperlane app", |
||||
"version": "1.0.0-beta3", |
||||
"dependencies": { |
||||
"@hyperlane-xyz/sdk": "1.0.0-beta3", |
||||
"@openzeppelin/contracts-upgradeable": "^4.8.0", |
||||
"ethers": "^5.6.8" |
||||
}, |
||||
"devDependencies": { |
||||
"@nomiclabs/hardhat-ethers": "^2.0.5", |
||||
"@nomiclabs/hardhat-waffle": "^2.0.2", |
||||
"@trivago/prettier-plugin-sort-imports": "^3.2.0", |
||||
"@typechain/ethers-v5": "10.0.0", |
||||
"@typechain/hardhat": "^6.0.0", |
||||
"@types/mocha": "^9.1.0", |
||||
"@typescript-eslint/eslint-plugin": "^5.27.0", |
||||
"@typescript-eslint/parser": "^5.27.0", |
||||
"chai": "^4.3.0", |
||||
"eslint": "^8.16.0", |
||||
"eslint-config-prettier": "^8.5.0", |
||||
"ethereum-waffle": "^3.4.4", |
||||
"hardhat": "^2.8.4", |
||||
"hardhat-gas-reporter": "^1.0.7", |
||||
"prettier": "^2.4.1", |
||||
"prettier-plugin-solidity": "^1.0.0-beta.5", |
||||
"solhint": "^3.3.2", |
||||
"solhint-plugin-prettier": "^0.0.5", |
||||
"solidity-coverage": "^0.7.14", |
||||
"ts-node": "^10.8.0", |
||||
"typechain": "8.0.0", |
||||
"typescript": "^4.7.2" |
||||
}, |
||||
"files": [ |
||||
"/dist", |
||||
"/contracts" |
||||
], |
||||
"homepage": "https://www.hyperlane.xyz", |
||||
"keywords": [ |
||||
"Hyperlane", |
||||
"HelloWorld", |
||||
"Solidity", |
||||
"Typescript" |
||||
], |
||||
"license": "Apache-2.0", |
||||
"main": "dist/src/index.js", |
||||
"packageManager": "yarn@3.2.0", |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "https://github.com/hyperlane-xyz/hyperlane-app-template" |
||||
}, |
||||
"scripts": { |
||||
"build": "hardhat compile && tsc", |
||||
"clean": "hardhat clean && rm -rf dist cache src/types", |
||||
"coverage": "hardhat coverage", |
||||
"lint": "eslint . --ext .ts", |
||||
"prettier": "prettier --write ./contracts ./src", |
||||
"test": "hardhat test ./src/test/**/*.test.ts", |
||||
"sync": "ts-node scripts/sync-with-template-repo.ts" |
||||
}, |
||||
"types": "dist/src/index.d.ts", |
||||
"resolutions": { |
||||
"underscore": "^1.13", |
||||
"fetch-ponyfill": "^7.1", |
||||
"lodash": "^4.17.21", |
||||
"async": "^2.6.4", |
||||
"undici": "^5.11" |
||||
}, |
||||
"stableVersion": "1.0.0-beta2" |
||||
} |
@ -1,91 +0,0 @@ |
||||
/** |
||||
* This script copies over files from this monorepo to the template repo |
||||
* It assumes the template repo is available locally at PATH_TO_TEMPLATE_REPO |
||||
* It will aggregate commits to this folder as the commit message |
||||
* Pull requests must still be created and merged on the template repo |
||||
* |
||||
* Usage: yarn sync |
||||
* Flags: --no-commit to skip automatic git committing and pushing |
||||
* |
||||
* Possible improvements: |
||||
* 1. Clone template automatically if it doesn't exist |
||||
* 2. Auto generate commit message based on changes since last sync |
||||
* 3. Run in CI using github token |
||||
*/ |
||||
import { execSync } from 'child_process'; |
||||
import { existsSync } from 'fs'; |
||||
|
||||
const SKIP_GIT_FLAG = '--no-commit'; |
||||
|
||||
const PATH_TO_TEMPLATE_REPO = '../../../hyperlane-app-template'; |
||||
|
||||
const SYNC_WHITELIST = [ |
||||
'contracts', |
||||
'src', |
||||
'.gitignore', |
||||
'.prettierignore', |
||||
'.solcover.js', |
||||
'.solhint.json', |
||||
'hardhat.config.ts', |
||||
'package.json', |
||||
'README.md', |
||||
]; |
||||
|
||||
async function main() { |
||||
console.info('Attempting to sync files with template repo'); |
||||
console.info('Using repo path:', PATH_TO_TEMPLATE_REPO); |
||||
|
||||
const args = process.argv.slice(2); |
||||
const skipGit = args.includes(SKIP_GIT_FLAG); |
||||
if (skipGit) { |
||||
console.info('Skip git flag set, will not run any git commands'); |
||||
} else { |
||||
console.info( |
||||
'Skip git flag not set, will automatically commit and push changes to new branch', |
||||
); |
||||
} |
||||
|
||||
if (!existsSync(PATH_TO_TEMPLATE_REPO)) { |
||||
throw new Error('No folder found at repo path'); |
||||
} |
||||
|
||||
const t = new Date(); |
||||
const date = `${t.getFullYear()}-${ |
||||
t.getMonth() + 1 |
||||
}-${t.getDate()}-${t.getHours()}-${t.getMinutes()}`;
|
||||
const branchName = `sync-${date}`; |
||||
|
||||
if (!skipGit) { |
||||
execSync(`git checkout main && git pull`, { cwd: PATH_TO_TEMPLATE_REPO }); |
||||
execSync(`git checkout -b ${branchName}`, { |
||||
cwd: PATH_TO_TEMPLATE_REPO, |
||||
}); |
||||
} |
||||
|
||||
for (const f of SYNC_WHITELIST) { |
||||
console.info(`Copying ${f}`); |
||||
execSync(`cp -r ${f} ${PATH_TO_TEMPLATE_REPO}`); |
||||
} |
||||
|
||||
console.info(`Running yarn to ensure up to date lockfile`); |
||||
execSync(`yarn install`, { cwd: PATH_TO_TEMPLATE_REPO }); |
||||
|
||||
if (!skipGit) { |
||||
console.info(`Committing changes`); |
||||
execSync(`git add . && git commit -m "Sync with monorepo"`, { |
||||
cwd: PATH_TO_TEMPLATE_REPO, |
||||
}); |
||||
execSync(`git push -u origin ${branchName}`, { |
||||
cwd: PATH_TO_TEMPLATE_REPO, |
||||
}); |
||||
console.info( |
||||
`Changes pushed to branch ${branchName}, please create pull request manually`, |
||||
); |
||||
} else { |
||||
console.info(`Please commit changes and create pull request manually`); |
||||
} |
||||
} |
||||
|
||||
main() |
||||
.then(() => console.info('Sync complete!')) |
||||
.catch((e) => console.error('Sync failed', e)); |
@ -1,107 +0,0 @@ |
||||
import { BigNumber, ethers } from 'ethers'; |
||||
|
||||
import { |
||||
ChainMap, |
||||
ChainName, |
||||
ChainNameToDomainId, |
||||
HyperlaneApp, |
||||
HyperlaneCore, |
||||
MultiProvider, |
||||
Remotes, |
||||
} from '@hyperlane-xyz/sdk'; |
||||
import { debug } from '@hyperlane-xyz/utils'; |
||||
|
||||
import { HelloWorldContracts } from './contracts'; |
||||
|
||||
type Counts = { |
||||
sent: number; |
||||
received: number; |
||||
}; |
||||
|
||||
export class HelloWorldApp< |
||||
Chain extends ChainName = ChainName, |
||||
> extends HyperlaneApp<HelloWorldContracts, Chain> { |
||||
constructor( |
||||
public readonly core: HyperlaneCore<Chain>, |
||||
contractsMap: ChainMap<Chain, HelloWorldContracts>, |
||||
multiProvider: MultiProvider<Chain>, |
||||
) { |
||||
super(contractsMap, multiProvider); |
||||
} |
||||
|
||||
async sendHelloWorld<From extends Chain>( |
||||
from: From, |
||||
to: Remotes<Chain, From>, |
||||
message: string, |
||||
value: BigNumber, |
||||
): Promise<ethers.ContractReceipt> { |
||||
const sender = this.getContracts(from).router; |
||||
const toDomain = ChainNameToDomainId[to]; |
||||
const chainConnection = this.multiProvider.getChainConnection(from); |
||||
|
||||
// apply gas buffer due to https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/634
|
||||
const estimated = await sender.estimateGas.sendHelloWorld( |
||||
toDomain, |
||||
message, |
||||
{ ...chainConnection.overrides, value }, |
||||
); |
||||
const gasLimit = estimated.mul(12).div(10); |
||||
|
||||
const tx = await sender.sendHelloWorld(toDomain, message, { |
||||
...chainConnection.overrides, |
||||
gasLimit, |
||||
value, |
||||
}); |
||||
debug('Sending hello message', { |
||||
from, |
||||
to, |
||||
message, |
||||
tx, |
||||
}); |
||||
return tx.wait(chainConnection.confirmations); |
||||
} |
||||
|
||||
async waitForMessageReceipt( |
||||
receipt: ethers.ContractReceipt, |
||||
): Promise<ethers.ContractReceipt[]> { |
||||
return this.core.waitForMessageProcessing(receipt); |
||||
} |
||||
|
||||
async waitForMessageProcessed( |
||||
receipt: ethers.ContractReceipt, |
||||
): Promise<void> { |
||||
return this.core.waitForMessageProcessed(receipt); |
||||
} |
||||
|
||||
async channelStats<From extends Chain>( |
||||
from: From, |
||||
to: Remotes<Chain, From>, |
||||
): Promise<Counts> { |
||||
const sent = await this.getContracts(from).router.sentTo( |
||||
ChainNameToDomainId[to], |
||||
); |
||||
const received = await this.getContracts(to).router.receivedFrom( |
||||
ChainNameToDomainId[from], |
||||
); |
||||
|
||||
return { sent: sent.toNumber(), received: received.toNumber() }; |
||||
} |
||||
|
||||
async stats(): Promise<Record<Chain, Record<Chain, Counts>>> { |
||||
const entries: Array<[Chain, Record<Chain, Counts>]> = await Promise.all( |
||||
this.chains().map(async (source) => { |
||||
const destinationEntries = await Promise.all( |
||||
this.remoteChains(source).map(async (destination) => [ |
||||
destination, |
||||
await this.channelStats(source, destination), |
||||
]), |
||||
); |
||||
return [ |
||||
source, |
||||
Object.fromEntries(destinationEntries) as Record<Chain, Counts>, |
||||
]; |
||||
}), |
||||
); |
||||
return Object.fromEntries(entries) as Record<Chain, Record<Chain, Counts>>; |
||||
} |
||||
} |
@ -1,11 +0,0 @@ |
||||
import { RouterContracts, RouterFactories } from '@hyperlane-xyz/sdk'; |
||||
|
||||
import { HelloWorld, HelloWorld__factory } from '../types'; |
||||
|
||||
export type HelloWorldFactories = RouterFactories<HelloWorld>; |
||||
|
||||
export const helloWorldFactories: HelloWorldFactories = { |
||||
router: new HelloWorld__factory(), |
||||
}; |
||||
|
||||
export type HelloWorldContracts = RouterContracts<HelloWorld>; |
@ -1,15 +0,0 @@ |
||||
import { ChainName, HyperlaneRouterChecker } from '@hyperlane-xyz/sdk'; |
||||
|
||||
import { HelloWorldApp } from '../app/app'; |
||||
import { HelloWorldContracts } from '../app/contracts'; |
||||
|
||||
import { HelloWorldConfig } from './config'; |
||||
|
||||
export class HelloWorldChecker< |
||||
Chain extends ChainName, |
||||
> extends HyperlaneRouterChecker< |
||||
Chain, |
||||
HelloWorldApp<Chain>, |
||||
HelloWorldConfig, |
||||
HelloWorldContracts |
||||
> {} |
@ -1,8 +0,0 @@ |
||||
import { RouterConfig, chainConnectionConfigs } from '@hyperlane-xyz/sdk'; |
||||
|
||||
export type HelloWorldConfig = RouterConfig; |
||||
|
||||
// SET DESIRED NETWORKS HERE
|
||||
export const prodConfigs = { |
||||
alfajores: chainConnectionConfigs.alfajores, |
||||
}; |
@ -1,44 +0,0 @@ |
||||
import { |
||||
ChainMap, |
||||
ChainName, |
||||
HyperlaneCore, |
||||
HyperlaneRouterDeployer, |
||||
MultiProvider, |
||||
} from '@hyperlane-xyz/sdk'; |
||||
|
||||
import { |
||||
HelloWorldContracts, |
||||
HelloWorldFactories, |
||||
helloWorldFactories, |
||||
} from '../app/contracts'; |
||||
|
||||
import { HelloWorldConfig } from './config'; |
||||
|
||||
export class HelloWorldDeployer< |
||||
Chain extends ChainName, |
||||
> extends HyperlaneRouterDeployer< |
||||
Chain, |
||||
HelloWorldConfig, |
||||
HelloWorldContracts, |
||||
HelloWorldFactories |
||||
> { |
||||
constructor( |
||||
multiProvider: MultiProvider<Chain>, |
||||
configMap: ChainMap<Chain, HelloWorldConfig>, |
||||
protected core: HyperlaneCore<Chain>, |
||||
) { |
||||
super(multiProvider, configMap, helloWorldFactories, {}); |
||||
} |
||||
|
||||
// Custom contract deployment logic can go here
|
||||
// If no custom logic is needed, call deployContract for the router
|
||||
async deployContracts(chain: Chain, config: HelloWorldConfig) { |
||||
const router = await this.deployContract(chain, 'router', [ |
||||
config.mailbox, |
||||
config.interchainGasPaymaster, |
||||
]); |
||||
return { |
||||
router, |
||||
}; |
||||
} |
||||
} |
@ -1,10 +0,0 @@ |
||||
export { HelloWorldChecker } from './deploy/check'; |
||||
export { HelloWorldConfig } from './deploy/config'; |
||||
export { HelloWorldDeployer } from './deploy/deploy'; |
||||
export { HelloWorldApp } from './app/app'; |
||||
export { |
||||
HelloWorldContracts, |
||||
HelloWorldFactories, |
||||
helloWorldFactories, |
||||
} from './app/contracts'; |
||||
export * as types from './types'; |
@ -1,50 +0,0 @@ |
||||
import { |
||||
ChainMap, |
||||
ChainName, |
||||
HyperlaneCore, |
||||
MultiProvider, |
||||
buildContracts, |
||||
getChainToOwnerMap, |
||||
objMap, |
||||
} from '@hyperlane-xyz/sdk'; |
||||
|
||||
import { HelloWorldApp } from '../app/app'; |
||||
import { HelloWorldContracts, helloWorldFactories } from '../app/contracts'; |
||||
import { HelloWorldChecker } from '../deploy/check'; |
||||
import { prodConfigs } from '../deploy/config'; |
||||
|
||||
// COPY FROM OUTPUT OF DEPLOYMENT SCRIPT OR IMPORT FROM ELSEWHERE
|
||||
const deploymentAddresses = {}; |
||||
|
||||
// SET CONTRACT OWNER ADDRESS HERE
|
||||
const ownerAddress = '0x123...'; |
||||
|
||||
async function check() { |
||||
console.info('Preparing utilities'); |
||||
const chainProviders = objMap(prodConfigs, (_, config) => ({ |
||||
provider: config.provider, |
||||
confirmations: config.confirmations, |
||||
overrides: config.overrides, |
||||
})); |
||||
const multiProvider = new MultiProvider(chainProviders); |
||||
|
||||
const contractsMap = buildContracts( |
||||
deploymentAddresses, |
||||
helloWorldFactories, |
||||
) as ChainMap<ChainName, HelloWorldContracts>; |
||||
|
||||
const core = HyperlaneCore.fromEnvironment('testnet2', multiProvider); |
||||
const app = new HelloWorldApp(core, contractsMap, multiProvider); |
||||
const config = core.extendWithConnectionClientConfig( |
||||
getChainToOwnerMap(prodConfigs, ownerAddress), |
||||
); |
||||
|
||||
console.info('Starting check'); |
||||
const helloWorldChecker = new HelloWorldChecker(multiProvider, app, config); |
||||
await helloWorldChecker.check(); |
||||
helloWorldChecker.expectEmpty(); |
||||
} |
||||
|
||||
check() |
||||
.then(() => console.info('Check complete')) |
||||
.catch(console.error); |
@ -1,39 +0,0 @@ |
||||
import { Wallet } from 'ethers'; |
||||
|
||||
import { |
||||
HyperlaneCore, |
||||
MultiProvider, |
||||
getChainToOwnerMap, |
||||
objMap, |
||||
serializeContracts, |
||||
} from '@hyperlane-xyz/sdk'; |
||||
|
||||
import { prodConfigs } from '../deploy/config'; |
||||
import { HelloWorldDeployer } from '../deploy/deploy'; |
||||
|
||||
async function main() { |
||||
console.info('Getting signer'); |
||||
const signer = new Wallet('SET KEY HERE OR CREATE YOUR OWN SIGNER'); |
||||
|
||||
console.info('Preparing utilities'); |
||||
const chainProviders = objMap(prodConfigs, (_, config) => ({ |
||||
...config, |
||||
signer: signer.connect(config.provider), |
||||
})); |
||||
const multiProvider = new MultiProvider(chainProviders); |
||||
|
||||
const core = HyperlaneCore.fromEnvironment('testnet2', multiProvider); |
||||
const config = core.extendWithConnectionClientConfig( |
||||
getChainToOwnerMap(prodConfigs, signer.address), |
||||
); |
||||
|
||||
const deployer = new HelloWorldDeployer(multiProvider, config, core); |
||||
const chainToContracts = await deployer.deploy(); |
||||
const addresses = serializeContracts(chainToContracts); |
||||
console.info('===Contract Addresses==='); |
||||
console.info(JSON.stringify(addresses)); |
||||
} |
||||
|
||||
main() |
||||
.then(() => console.info('Deploy complete')) |
||||
.catch(console.error); |
@ -1,56 +0,0 @@ |
||||
import '@nomiclabs/hardhat-waffle'; |
||||
import { ethers } from 'hardhat'; |
||||
|
||||
import { |
||||
ChainMap, |
||||
MultiProvider, |
||||
TestChainNames, |
||||
TestCoreApp, |
||||
TestCoreDeployer, |
||||
getChainToOwnerMap, |
||||
getTestMultiProvider, |
||||
testChainConnectionConfigs, |
||||
} from '@hyperlane-xyz/sdk'; |
||||
|
||||
import { HelloWorldApp } from '../app/app'; |
||||
import { HelloWorldContracts } from '../app/contracts'; |
||||
import { HelloWorldChecker } from '../deploy/check'; |
||||
import { HelloWorldConfig } from '../deploy/config'; |
||||
import { HelloWorldDeployer } from '../deploy/deploy'; |
||||
|
||||
describe('deploy', async () => { |
||||
let multiProvider: MultiProvider<TestChainNames>; |
||||
let core: TestCoreApp; |
||||
let config: ChainMap<TestChainNames, HelloWorldConfig>; |
||||
let deployer: HelloWorldDeployer<TestChainNames>; |
||||
let contracts: Record<TestChainNames, HelloWorldContracts>; |
||||
let app: HelloWorldApp<TestChainNames>; |
||||
|
||||
before(async () => { |
||||
const [signer] = await ethers.getSigners(); |
||||
multiProvider = getTestMultiProvider(signer); |
||||
|
||||
const coreDeployer = new TestCoreDeployer(multiProvider); |
||||
const coreContractsMaps = await coreDeployer.deploy(); |
||||
core = new TestCoreApp(coreContractsMaps, multiProvider); |
||||
config = core.extendWithConnectionClientConfig( |
||||
getChainToOwnerMap(testChainConnectionConfigs, signer.address), |
||||
); |
||||
deployer = new HelloWorldDeployer(multiProvider, config, core); |
||||
}); |
||||
|
||||
it('deploys', async () => { |
||||
contracts = await deployer.deploy(); |
||||
}); |
||||
|
||||
it('builds app', async () => { |
||||
contracts = await deployer.deploy(); |
||||
app = new HelloWorldApp(core, contracts, multiProvider); |
||||
}); |
||||
|
||||
it('checks', async () => { |
||||
const checker = new HelloWorldChecker(multiProvider, app, config); |
||||
await checker.check(); |
||||
checker.expectEmpty(); |
||||
}); |
||||
}); |
@ -1,83 +0,0 @@ |
||||
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; |
||||
import { expect } from 'chai'; |
||||
import { ethers } from 'hardhat'; |
||||
|
||||
import { |
||||
ChainMap, |
||||
ChainNameToDomainId, |
||||
MultiProvider, |
||||
TestChainNames, |
||||
TestCoreApp, |
||||
TestCoreDeployer, |
||||
getChainToOwnerMap, |
||||
getTestMultiProvider, |
||||
testChainConnectionConfigs, |
||||
} from '@hyperlane-xyz/sdk'; |
||||
|
||||
import { HelloWorldConfig } from '../deploy/config'; |
||||
import { HelloWorldDeployer } from '../deploy/deploy'; |
||||
import { HelloWorld } from '../types'; |
||||
|
||||
describe('HelloWorld', async () => { |
||||
const localChain = 'test1'; |
||||
const remoteChain = 'test2'; |
||||
const localDomain = ChainNameToDomainId[localChain]; |
||||
const remoteDomain = ChainNameToDomainId[remoteChain]; |
||||
|
||||
let signer: SignerWithAddress; |
||||
let local: HelloWorld; |
||||
let remote: HelloWorld; |
||||
let multiProvider: MultiProvider<TestChainNames>; |
||||
let coreApp: TestCoreApp; |
||||
let config: ChainMap<TestChainNames, HelloWorldConfig>; |
||||
|
||||
before(async () => { |
||||
[signer] = await ethers.getSigners(); |
||||
|
||||
multiProvider = getTestMultiProvider(signer); |
||||
|
||||
const coreDeployer = new TestCoreDeployer(multiProvider); |
||||
const coreContractsMaps = await coreDeployer.deploy(); |
||||
coreApp = new TestCoreApp(coreContractsMaps, multiProvider); |
||||
config = coreApp.extendWithConnectionClientConfig( |
||||
getChainToOwnerMap(testChainConnectionConfigs, signer.address), |
||||
); |
||||
}); |
||||
|
||||
beforeEach(async () => { |
||||
const helloWorld = new HelloWorldDeployer(multiProvider, config, coreApp); |
||||
const contracts = await helloWorld.deploy(); |
||||
|
||||
local = contracts[localChain].router; |
||||
remote = contracts[remoteChain].router; |
||||
|
||||
// The all counts start empty
|
||||
expect(await local.sent()).to.equal(0); |
||||
expect(await local.received()).to.equal(0); |
||||
expect(await remote.sent()).to.equal(0); |
||||
expect(await remote.received()).to.equal(0); |
||||
}); |
||||
|
||||
it('sends a message', async () => { |
||||
await expect(local.sendHelloWorld(remoteDomain, 'Hello')).to.emit( |
||||
local, |
||||
'SentHelloWorld', |
||||
); |
||||
// The sent counts are correct
|
||||
expect(await local.sent()).to.equal(1); |
||||
expect(await local.sentTo(remoteDomain)).to.equal(1); |
||||
// The received counts are correct
|
||||
expect(await local.received()).to.equal(0); |
||||
}); |
||||
|
||||
it('handles a message', async () => { |
||||
await local.sendHelloWorld(remoteDomain, 'World'); |
||||
// Mock processing of the message by Hyperlane
|
||||
await coreApp.processOutboundMessages(localChain); |
||||
// The initial message has been dispatched.
|
||||
expect(await local.sent()).to.equal(1); |
||||
// The initial message has been processed.
|
||||
expect(await remote.received()).to.equal(1); |
||||
expect(await remote.receivedFrom(localDomain)).to.equal(1); |
||||
}); |
||||
}); |
@ -1,11 +0,0 @@ |
||||
{ |
||||
"extends": "../../tsconfig.json", |
||||
"compilerOptions": { |
||||
"outDir": "./dist/", |
||||
"rootDir": "./", |
||||
"noImplicitAny": false, |
||||
}, |
||||
"exclude": ["./node_modules/", "./dist/", "./src/types/hardhat.d.ts"], |
||||
"include": ["./src/"], |
||||
"files": ["hardhat.config.ts"] |
||||
} |
@ -0,0 +1 @@ |
||||
Subproject commit a294fdf5ea498a018178fd90744c9c09276d9686 |
@ -1,6 +0,0 @@ |
||||
node_modules |
||||
dist |
||||
coverage |
||||
types |
||||
hardhat.config.ts |
||||
scripts |
@ -1,31 +0,0 @@ |
||||
{ |
||||
"env": { |
||||
"node": true, |
||||
"browser": true, |
||||
"es2021": true |
||||
}, |
||||
"root": true, |
||||
"parser": "@typescript-eslint/parser", |
||||
"parserOptions": { |
||||
"ecmaVersion": 12, |
||||
"sourceType": "module", |
||||
"project": "./tsconfig.json" |
||||
}, |
||||
"plugins": ["@typescript-eslint"], |
||||
"extends": [ |
||||
"eslint:recommended", |
||||
"plugin:@typescript-eslint/recommended", |
||||
"prettier" |
||||
], |
||||
"rules": { |
||||
"no-eval": ["error"], |
||||
"no-ex-assign": ["error"], |
||||
"no-constant-condition": ["off"], |
||||
"@typescript-eslint/ban-ts-comment": ["off"], |
||||
"@typescript-eslint/explicit-module-boundary-types": ["off"], |
||||
"@typescript-eslint/no-explicit-any": ["off"], |
||||
"@typescript-eslint/no-floating-promises": ["off"], |
||||
"@typescript-eslint/no-non-null-assertion": ["off"], |
||||
"@typescript-eslint/no-require-imports": ["warn"] |
||||
} |
||||
} |
@ -1,9 +0,0 @@ |
||||
node_modules/ |
||||
cache/ |
||||
artifacts/ |
||||
types/ |
||||
dist/ |
||||
coverage/ |
||||
coverage.json |
||||
*.swp |
||||
.yarn/install-state.gz |
@ -1,2 +0,0 @@ |
||||
src/types |
||||
test/outputs |
@ -1,21 +0,0 @@ |
||||
{ |
||||
"tabWidth": 2, |
||||
"singleQuote": true, |
||||
"trailingComma": "all", |
||||
"overrides": [ |
||||
{ |
||||
"files": "*.sol", |
||||
"options": { |
||||
"printWidth": 80, |
||||
"tabWidth": 4, |
||||
"useTabs": false, |
||||
"singleQuote": false, |
||||
"bracketSpacing": false, |
||||
"explicitTypes": "always" |
||||
} |
||||
} |
||||
], |
||||
"importOrder": ["^@hyperlane-xyz/(.*)$", "^../(.*)$", "^./(.*)$"], |
||||
"importOrderSeparation": true, |
||||
"importOrderSortSpecifiers": true |
||||
} |
@ -1,3 +0,0 @@ |
||||
module.exports = { |
||||
skipFiles: ['test'], |
||||
}; |
@ -1,8 +0,0 @@ |
||||
{ |
||||
"extends": "solhint:recommended", |
||||
"rules": { |
||||
"compiler-version": ["error", ">=0.6.0"], |
||||
"func-visibility": ["warn", {"ignoreConstructors":true}], |
||||
"not-rely-on-time": "off" |
||||
} |
||||
} |
@ -1,64 +0,0 @@ |
||||
// SPDX-License-Identifier: Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
import {TokenRouter} from "./libs/TokenRouter.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 HypERC20 is ERC20Upgradeable, TokenRouter { |
||||
/** |
||||
* @notice Initializes the Hyperlane router, ERC20 metadata, and mints initial supply to deployer. |
||||
* @param _mailbox The address of the mailbox contract. |
||||
* @param _interchainGasPaymaster The address of the interchain gas paymaster contract. |
||||
* @param _totalSupply The initial supply of the token. |
||||
* @param _name The name of the token. |
||||
* @param _symbol The symbol of the token. |
||||
*/ |
||||
function initialize( |
||||
address _mailbox, |
||||
address _interchainGasPaymaster, |
||||
uint256 _totalSupply, |
||||
string memory _name, |
||||
string memory _symbol |
||||
) external initializer { |
||||
// transfers ownership to `msg.sender` |
||||
__HyperlaneConnectionClient_initialize( |
||||
_mailbox, |
||||
_interchainGasPaymaster |
||||
); |
||||
|
||||
// Initialize ERC20 metadata |
||||
__ERC20_init(_name, _symbol); |
||||
_mint(msg.sender, _totalSupply); |
||||
} |
||||
|
||||
/** |
||||
* @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); |
||||
} |
||||
} |
@ -1,61 +0,0 @@ |
||||
// SPDX-License-Identifier: Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
import {TokenRouter} from "./libs/TokenRouter.sol"; |
||||
|
||||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
||||
|
||||
/** |
||||
* @title Hyperlane ERC20 Token Collateral that wraps an existing ERC20 with remote transfer functionality. |
||||
* @author Abacus Works |
||||
*/ |
||||
contract HypERC20Collateral is TokenRouter { |
||||
IERC20 public immutable wrappedToken; |
||||
|
||||
constructor(address erc20) { |
||||
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. |
||||
*/ |
||||
function initialize(address _mailbox, address _interchainGasPaymaster) |
||||
external |
||||
initializer |
||||
{ |
||||
__HyperlaneConnectionClient_initialize( |
||||
_mailbox, |
||||
_interchainGasPaymaster |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* @dev Transfers `_amount` of `wrappedToken` from `msg.sender` to this contract. |
||||
* @inheritdoc TokenRouter |
||||
*/ |
||||
function _transferFromSender(uint256 _amount) |
||||
internal |
||||
override |
||||
returns (bytes memory) |
||||
{ |
||||
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"); |
||||
} |
||||
} |
@ -1,66 +0,0 @@ |
||||
// SPDX-License-Identifier: Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
import {TokenRouter} from "./libs/TokenRouter.sol"; |
||||
|
||||
import {ERC721EnumerableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol"; |
||||
|
||||
/** |
||||
* @title Hyperlane ERC721 Token Router that extends ERC721 with remote transfer functionality. |
||||
* @author Abacus Works |
||||
*/ |
||||
contract HypERC721 is ERC721EnumerableUpgradeable, TokenRouter { |
||||
/** |
||||
* @notice Initializes the Hyperlane router, ERC721 metadata, and mints initial supply to deployer. |
||||
* @param _mailbox The address of the mailbox contract. |
||||
* @param _interchainGasPaymaster The address of the interchain gas paymaster contract. |
||||
* @param _mintAmount The amount of NFTs to mint to `msg.sender`. |
||||
* @param _name The name of the token. |
||||
* @param _symbol The symbol of the token. |
||||
*/ |
||||
function initialize( |
||||
address _mailbox, |
||||
address _interchainGasPaymaster, |
||||
uint256 _mintAmount, |
||||
string memory _name, |
||||
string memory _symbol |
||||
) external initializer { |
||||
// transfers ownership to `msg.sender` |
||||
__HyperlaneConnectionClient_initialize( |
||||
_mailbox, |
||||
_interchainGasPaymaster |
||||
); |
||||
|
||||
__ERC721_init(_name, _symbol); |
||||
for (uint256 i = 0; i < _mintAmount; i++) { |
||||
_mint(msg.sender, i); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @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 |
||||
} |
||||
|
||||
/** |
||||
* @dev Mints `_tokenId` to `_recipient`. |
||||
* @inheritdoc TokenRouter |
||||
*/ |
||||
function _transferTo( |
||||
address _recipient, |
||||
uint256 _tokenId, |
||||
bytes calldata // no metadata |
||||
) internal virtual override { |
||||
_mint(_recipient, _tokenId); |
||||
} |
||||
} |
@ -1,59 +0,0 @@ |
||||
// SPDX-License-Identifier: Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
import {TokenRouter} from "./libs/TokenRouter.sol"; |
||||
|
||||
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; |
||||
|
||||
/** |
||||
* @title Hyperlane ERC721 Token Collateral that wraps an existing ERC721 with remote transfer functionality. |
||||
* @author Abacus Works |
||||
*/ |
||||
contract HypERC721Collateral is TokenRouter { |
||||
address public immutable wrappedToken; |
||||
|
||||
constructor(address 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. |
||||
*/ |
||||
function initialize(address _mailbox, address _interchainGasPaymaster) |
||||
external |
||||
initializer |
||||
{ |
||||
__HyperlaneConnectionClient_initialize( |
||||
_mailbox, |
||||
_interchainGasPaymaster |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* @dev Transfers `_tokenId` of `wrappedToken` from `msg.sender` to this contract. |
||||
* @inheritdoc TokenRouter |
||||
*/ |
||||
function _transferFromSender(uint256 _tokenId) |
||||
internal |
||||
virtual |
||||
override |
||||
returns (bytes memory) |
||||
{ |
||||
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); |
||||
} |
||||
} |
@ -1,29 +0,0 @@ |
||||
// 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)); |
||||
} |
||||
} |
@ -1,79 +0,0 @@ |
||||
// 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, |
||||
uint256 batchSize |
||||
) internal override(ERC721EnumerableUpgradeable, ERC721Upgradeable) { |
||||
ERC721EnumerableUpgradeable._beforeTokenTransfer( |
||||
from, |
||||
to, |
||||
tokenId, |
||||
batchSize |
||||
); |
||||
} |
||||
|
||||
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); |
||||
} |
||||
} |
@ -1,33 +0,0 @@ |
||||
// SPDX-License-Identifier: MIT OR Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
library Message { |
||||
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) { |
||||
return bytes32(message[0:32]); |
||||
} |
||||
|
||||
function amount(bytes calldata message) internal pure returns (uint256) { |
||||
return uint256(bytes32(message[32:64])); |
||||
} |
||||
|
||||
// alias for ERC721 |
||||
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:]; |
||||
} |
||||
} |
@ -1,99 +0,0 @@ |
||||
// SPDX-License-Identifier: Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
import {Router} from "@hyperlane-xyz/core/contracts/Router.sol"; |
||||
import {TypeCasts} from "@hyperlane-xyz/core/contracts/libs/TypeCasts.sol"; |
||||
import {Message} from "./Message.sol"; |
||||
|
||||
/** |
||||
* @title Hyperlane Token Router that extends Router with abstract token (ERC20/ERC721) remote transfer functionality. |
||||
* @author Abacus Works |
||||
*/ |
||||
abstract contract TokenRouter is Router { |
||||
using TypeCasts for bytes32; |
||||
using Message for bytes; |
||||
|
||||
/** |
||||
* @dev Emitted on `transferRemote` when a transfer message is dispatched. |
||||
* @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 burnt on the origin chain. |
||||
*/ |
||||
event SentTransferRemote( |
||||
uint32 indexed destination, |
||||
bytes32 indexed recipient, |
||||
uint256 amount |
||||
); |
||||
|
||||
/** |
||||
* @dev Emitted on `_handle` when a transfer message is processed. |
||||
* @param origin The identifier of the origin chain. |
||||
* @param recipient The address of the recipient on the destination chain. |
||||
* @param amount The amount of tokens minted on the destination chain. |
||||
*/ |
||||
event ReceivedTransferRemote( |
||||
uint32 indexed origin, |
||||
bytes32 indexed recipient, |
||||
uint256 amount |
||||
); |
||||
|
||||
/** |
||||
* @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 _amountOrId The amount or identifier of tokens to be sent to the remote recipient. |
||||
*/ |
||||
function transferRemote( |
||||
uint32 _destination, |
||||
bytes32 _recipient, |
||||
uint256 _amountOrId |
||||
) external payable { |
||||
bytes memory metadata = _transferFromSender(_amountOrId); |
||||
_dispatch( |
||||
_destination, |
||||
Message.format(_recipient, _amountOrId, metadata) |
||||
); |
||||
emit SentTransferRemote(_destination, _recipient, _amountOrId); |
||||
} |
||||
|
||||
/** |
||||
* @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. |
||||
* @dev Emits `ReceivedTransferRemote` event on the destination chain. |
||||
* @param _origin The identifier of the origin chain. |
||||
* @param _message The encoded remote transfer message containing the recipient address and amount. |
||||
*/ |
||||
function _handle( |
||||
uint32 _origin, |
||||
bytes32, |
||||
bytes calldata _message |
||||
) internal override { |
||||
bytes32 recipient = _message.recipient(); |
||||
uint256 amount = _message.amount(); |
||||
bytes calldata metadata = _message.metadata(); |
||||
_transferTo(recipient.bytes32ToAddress(), amount, metadata); |
||||
emit ReceivedTransferRemote(_origin, recipient, amount); |
||||
} |
||||
|
||||
/** |
||||
* @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; |
||||
} |
@ -1,14 +0,0 @@ |
||||
// SPDX-License-Identifier: Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; |
||||
|
||||
contract ERC20Test is ERC20 { |
||||
constructor( |
||||
string memory name, |
||||
string memory symbol, |
||||
uint256 totalSupply |
||||
) ERC20(name, symbol) { |
||||
_mint(msg.sender, totalSupply); |
||||
} |
||||
} |
@ -1,20 +0,0 @@ |
||||
// SPDX-License-Identifier: Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; |
||||
|
||||
contract ERC721Test is ERC721 { |
||||
constructor( |
||||
string memory name, |
||||
string memory symbol, |
||||
uint256 _mintAmount |
||||
) ERC721(name, symbol) { |
||||
for (uint256 i = 0; i < _mintAmount; i++) { |
||||
_mint(msg.sender, i); |
||||
} |
||||
} |
||||
|
||||
function _baseURI() internal pure override returns (string memory) { |
||||
return "TEST-BASE-URI"; |
||||
} |
||||
} |
@ -1,28 +0,0 @@ |
||||
import '@nomiclabs/hardhat-ethers'; |
||||
import '@nomiclabs/hardhat-waffle'; |
||||
import '@typechain/hardhat'; |
||||
import 'hardhat-gas-reporter'; |
||||
import 'solidity-coverage'; |
||||
|
||||
/** |
||||
* @type import('hardhat/config').HardhatUserConfig |
||||
*/ |
||||
module.exports = { |
||||
solidity: { |
||||
version: '0.8.17', |
||||
settings: { |
||||
optimizer: { |
||||
enabled: true, |
||||
runs: 999_999, |
||||
}, |
||||
}, |
||||
}, |
||||
gasReporter: { |
||||
currency: 'USD', |
||||
}, |
||||
typechain: { |
||||
outDir: './src/types', |
||||
target: 'ethers-v5', |
||||
alwaysGenerateOverloads: false, // should overloads with full signatures like deposit(uint256) be generated always, even if there are no overloads?
|
||||
}, |
||||
}; |
@ -1,63 +0,0 @@ |
||||
{ |
||||
"name": "@hyperlane-xyz/hyperlane-token", |
||||
"description": "A template for interchain ERC20 and ERC721 tokens using Hyperlane", |
||||
"version": "1.0.0-beta3", |
||||
"dependencies": { |
||||
"@hyperlane-xyz/core": "1.0.0-beta3", |
||||
"@hyperlane-xyz/sdk": "1.0.0-beta3", |
||||
"@hyperlane-xyz/utils": "1.0.0-beta3", |
||||
"@openzeppelin/contracts-upgradeable": "^4.8.0", |
||||
"ethers": "^5.6.8" |
||||
}, |
||||
"devDependencies": { |
||||
"@nomiclabs/hardhat-ethers": "^2.0.5", |
||||
"@nomiclabs/hardhat-waffle": "^2.0.2", |
||||
"@trivago/prettier-plugin-sort-imports": "^3.2.0", |
||||
"@typechain/ethers-v5": "10.0.0", |
||||
"@typechain/hardhat": "^6.0.0", |
||||
"@types/mocha": "^9.1.0", |
||||
"@typescript-eslint/eslint-plugin": "^5.27.0", |
||||
"@typescript-eslint/parser": "^5.27.0", |
||||
"chai": "^4.3.0", |
||||
"eslint": "^8.16.0", |
||||
"eslint-config-prettier": "^8.5.0", |
||||
"ethereum-waffle": "^3.4.4", |
||||
"hardhat": "^2.8.4", |
||||
"hardhat-gas-reporter": "^1.0.7", |
||||
"prettier": "^2.4.1", |
||||
"prettier-plugin-solidity": "^1.0.0-beta.5", |
||||
"solhint": "^3.3.2", |
||||
"solhint-plugin-prettier": "^0.0.5", |
||||
"solidity-coverage": "^0.7.14", |
||||
"ts-node": "^10.8.0", |
||||
"typechain": "8.0.0", |
||||
"typescript": "^4.7.2" |
||||
}, |
||||
"files": [ |
||||
"/dist", |
||||
"/contracts" |
||||
], |
||||
"homepage": "https://www.hyperlane.xyz", |
||||
"keywords": [ |
||||
"Hyperlane", |
||||
"Solidity", |
||||
"Token" |
||||
], |
||||
"license": "Apache-2.0", |
||||
"main": "dist/index.js", |
||||
"packageManager": "yarn@3.2.0", |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "https://github.com/hyperlane-xyz/hyperlane-token" |
||||
}, |
||||
"scripts": { |
||||
"clean": "hardhat clean && rm -rf dist cache src/types", |
||||
"build": "hardhat compile && tsc", |
||||
"coverage": "hardhat coverage", |
||||
"lint": "eslint . --ext .ts", |
||||
"prettier": "prettier --write ./contracts ./test", |
||||
"test": "hardhat test ./test/*.test.ts" |
||||
}, |
||||
"types": "dist/index.d.ts", |
||||
"stableVersion": "1.0.0-beta2" |
||||
} |
@ -1,65 +0,0 @@ |
||||
import { Wallet, ethers } from 'ethers'; |
||||
|
||||
import { |
||||
Chains, |
||||
HyperlaneCore, |
||||
MultiProvider, |
||||
objMap, |
||||
} from '@hyperlane-xyz/sdk'; |
||||
import { RouterConfig, chainConnectionConfigs } from '@hyperlane-xyz/sdk'; |
||||
|
||||
import { TokenConfig, TokenType } from '../src/config'; |
||||
import { HypERC20Deployer } from '../src/deploy'; |
||||
|
||||
const connectionConfigs = { |
||||
goerli: { |
||||
...chainConnectionConfigs.goerli, |
||||
provider: new ethers.providers.JsonRpcProvider( |
||||
'https://eth-goerli.public.blastapi.io', |
||||
5, |
||||
), |
||||
}, |
||||
fuji: chainConnectionConfigs.fuji, |
||||
alfajores: chainConnectionConfigs.alfajores, |
||||
moonbasealpha: chainConnectionConfigs.moonbasealpha, |
||||
}; |
||||
|
||||
async function deployNFTWrapper() { |
||||
console.info('Getting signer'); |
||||
const signer = new Wallet( |
||||
'3ed2c141ec02887887e94c1fbe5647fe5741c038deca248afbaaccf2c27d9258', |
||||
); |
||||
|
||||
const multiProvider = new MultiProvider(connectionConfigs); |
||||
multiProvider.rotateSigner(signer); |
||||
const core = HyperlaneCore.fromEnvironment('testnet2', multiProvider); |
||||
|
||||
const config = objMap( |
||||
connectionConfigs, |
||||
(chain, _) => |
||||
({ |
||||
type: TokenType.synthetic, |
||||
name: 'Dai', |
||||
symbol: 'DAI', |
||||
totalSupply: 0, |
||||
owner: signer.address, |
||||
mailbox: '0x1d3aAC239538e6F1831C8708803e61A9EA299Eec', |
||||
interchainGasPaymaster: |
||||
core.getContracts(chain).interchainGasPaymaster.address, |
||||
} as TokenConfig & RouterConfig), |
||||
); |
||||
config.goerli = { |
||||
type: TokenType.collateral, |
||||
token: '0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6', |
||||
owner: signer.address, |
||||
mailbox: '0x1d3aAC239538e6F1831C8708803e61A9EA299Eec', |
||||
interchainGasPaymaster: core.getContracts(Chains.goerli) |
||||
.interchainGasPaymaster.address, |
||||
} as TokenConfig & RouterConfig; |
||||
|
||||
const deployer = new HypERC20Deployer(multiProvider, config, core); |
||||
|
||||
await deployer.deploy(); |
||||
} |
||||
|
||||
deployNFTWrapper().then(console.log).catch(console.error); |
@ -1,43 +0,0 @@ |
||||
import { |
||||
ChainName, |
||||
HyperlaneApp, |
||||
objMap, |
||||
promiseObjAll, |
||||
} from '@hyperlane-xyz/sdk'; |
||||
|
||||
import { HypERC20Contracts, HypERC721Contracts } from './contracts'; |
||||
|
||||
export class HypERC20App<Chain extends ChainName> extends HyperlaneApp< |
||||
HypERC20Contracts, |
||||
Chain |
||||
> { |
||||
getSecurityModules = () => |
||||
promiseObjAll( |
||||
objMap(this.contractsMap, (_, contracts) => |
||||
contracts.router.interchainSecurityModule(), |
||||
), |
||||
); |
||||
|
||||
getOwners = () => |
||||
promiseObjAll( |
||||
objMap(this.contractsMap, (_, contracts) => contracts.router.owner()), |
||||
); |
||||
} |
||||
|
||||
// TODO: dedupe?
|
||||
export class HypERC721App<Chain extends ChainName> extends HyperlaneApp< |
||||
HypERC721Contracts, |
||||
Chain |
||||
> { |
||||
getSecurityModules = () => |
||||
promiseObjAll( |
||||
objMap(this.contractsMap, (_, contracts) => |
||||
contracts.router.interchainSecurityModule(), |
||||
), |
||||
); |
||||
|
||||
getOwners = () => |
||||
promiseObjAll( |
||||
objMap(this.contractsMap, (_, contracts) => contracts.router.owner()), |
||||
); |
||||
} |
@ -1,42 +0,0 @@ |
||||
import { ethers } from 'ethers'; |
||||
|
||||
import { RouterConfig } from '@hyperlane-xyz/sdk'; |
||||
|
||||
export enum TokenType { |
||||
synthetic, |
||||
syntheticUri, |
||||
collateral, |
||||
collateralUri, |
||||
} |
||||
|
||||
export type SyntheticConfig = { |
||||
type: TokenType.synthetic | TokenType.syntheticUri; |
||||
name: string; |
||||
symbol: string; |
||||
totalSupply: ethers.BigNumberish; |
||||
}; |
||||
export type CollateralConfig = { |
||||
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 === 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; |
||||
|
||||
export type HypERC721Config = RouterConfig & TokenConfig; |
||||
export type HypERC721CollateralConfig = RouterConfig & CollateralConfig; |
@ -1,14 +0,0 @@ |
||||
import { RouterContracts } from '@hyperlane-xyz/sdk'; |
||||
|
||||
import { |
||||
HypERC20, |
||||
HypERC20Collateral, |
||||
HypERC721, |
||||
HypERC721Collateral, |
||||
HypERC721URICollateral, |
||||
} from './types'; |
||||
|
||||
export type HypERC20Contracts = RouterContracts<HypERC20 | HypERC20Collateral>; |
||||
export type HypERC721Contracts = RouterContracts< |
||||
HypERC721 | HypERC721Collateral | HypERC721URICollateral |
||||
>; |
@ -1,114 +0,0 @@ |
||||
import { ChainName, HyperlaneRouterDeployer } from '@hyperlane-xyz/sdk'; |
||||
|
||||
import { |
||||
HypERC20CollateralConfig, |
||||
HypERC20Config, |
||||
HypERC721CollateralConfig, |
||||
HypERC721Config, |
||||
isCollateralConfig, |
||||
isUriConfig, |
||||
} from './config'; |
||||
import { HypERC20Contracts, HypERC721Contracts } from './contracts'; |
||||
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
|
||||
> extends HyperlaneRouterDeployer< |
||||
Chain, |
||||
HypERC20Config | HypERC20CollateralConfig, |
||||
HypERC20Contracts, |
||||
any // RouterFactories doesn't work well when router has multiple types
|
||||
> { |
||||
async deployContracts( |
||||
chain: Chain, |
||||
config: HypERC20Config | HypERC20CollateralConfig, |
||||
) { |
||||
const connection = this.multiProvider.getChainConnection(chain); |
||||
if (isCollateralConfig(config)) { |
||||
const router = await this.deployContractFromFactory( |
||||
chain, |
||||
new HypERC20Collateral__factory(), |
||||
'HypERC20Collateral', |
||||
[config.token], |
||||
); |
||||
await connection.handleTx( |
||||
router.initialize(config.mailbox, config.interchainGasPaymaster), |
||||
); |
||||
return { router }; |
||||
} else { |
||||
const router = await this.deployContractFromFactory( |
||||
chain, |
||||
new HypERC20__factory(), |
||||
'HypERC20', |
||||
[], |
||||
); |
||||
await connection.handleTx( |
||||
router.initialize( |
||||
config.mailbox, |
||||
config.interchainGasPaymaster, |
||||
config.totalSupply, |
||||
config.name, |
||||
config.symbol, |
||||
), |
||||
); |
||||
return { router }; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// TODO: dedupe?
|
||||
export class HypERC721Deployer< |
||||
Chain extends ChainName, |
||||
> extends HyperlaneRouterDeployer< |
||||
Chain, |
||||
HypERC721Config | HypERC721CollateralConfig, |
||||
HypERC721Contracts, |
||||
any |
||||
> { |
||||
async deployContracts( |
||||
chain: Chain, |
||||
config: HypERC721Config | HypERC721CollateralConfig, |
||||
) { |
||||
const connection = this.multiProvider.getChainConnection(chain); |
||||
if (isCollateralConfig(config)) { |
||||
const router = await this.deployContractFromFactory( |
||||
chain, |
||||
isUriConfig(config) |
||||
? new HypERC721URICollateral__factory() |
||||
: new HypERC721Collateral__factory(), |
||||
`HypERC721${isUriConfig(config) ? 'URI' : ''}Collateral`, |
||||
[config.token], |
||||
); |
||||
await connection.handleTx( |
||||
router.initialize(config.mailbox, config.interchainGasPaymaster), |
||||
); |
||||
return { router }; |
||||
} else { |
||||
const router = await this.deployContractFromFactory( |
||||
chain, |
||||
isUriConfig(config) |
||||
? new HypERC721URIStorage__factory() |
||||
: new HypERC721__factory(), |
||||
`HypERC721${isUriConfig(config) ? 'URIStorage' : ''}`, |
||||
[], |
||||
); |
||||
await connection.handleTx( |
||||
router.initialize( |
||||
config.mailbox, |
||||
config.interchainGasPaymaster, |
||||
config.totalSupply, |
||||
config.name, |
||||
config.symbol, |
||||
), |
||||
); |
||||
return { router }; |
||||
} |
||||
} |
||||
} |
@ -1,219 +0,0 @@ |
||||
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; |
||||
import '@nomiclabs/hardhat-waffle'; |
||||
import { expect } from 'chai'; |
||||
import { ethers } from 'hardhat'; |
||||
|
||||
import { |
||||
ChainMap, |
||||
ChainNameToDomainId, |
||||
TestChainNames, |
||||
TestCoreApp, |
||||
TestCoreDeployer, |
||||
getTestMultiProvider, |
||||
objMap, |
||||
} from '@hyperlane-xyz/sdk'; |
||||
import { utils } from '@hyperlane-xyz/utils'; |
||||
|
||||
import { |
||||
HypERC20CollateralConfig, |
||||
HypERC20Config, |
||||
SyntheticConfig, |
||||
TokenType, |
||||
} from '../src/config'; |
||||
import { HypERC20Contracts } from '../src/contracts'; |
||||
import { HypERC20Deployer } from '../src/deploy'; |
||||
import { |
||||
ERC20, |
||||
ERC20Test__factory, |
||||
ERC20__factory, |
||||
HypERC20, |
||||
HypERC20Collateral, |
||||
} from '../src/types'; |
||||
|
||||
const localChain = 'test1'; |
||||
const remoteChain = 'test2'; |
||||
const localDomain = ChainNameToDomainId[localChain]; |
||||
const remoteDomain = ChainNameToDomainId[remoteChain]; |
||||
const totalSupply = 3000; |
||||
const amount = 10; |
||||
const testInterchainGasPayment = 123456789; |
||||
|
||||
const tokenConfig: SyntheticConfig = { |
||||
type: TokenType.synthetic, |
||||
name: 'HypERC20', |
||||
symbol: 'HYP', |
||||
totalSupply, |
||||
}; |
||||
|
||||
for (const withCollateral of [true, false]) { |
||||
describe(`HypERC20${withCollateral ? 'Collateral' : ''}`, async () => { |
||||
let owner: SignerWithAddress; |
||||
let recipient: SignerWithAddress; |
||||
let core: TestCoreApp; |
||||
let deployer: HypERC20Deployer<TestChainNames>; |
||||
let contracts: Record<TestChainNames, HypERC20Contracts>; |
||||
let local: HypERC20 | HypERC20Collateral; |
||||
let remote: HypERC20 | HypERC20Collateral; |
||||
|
||||
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, |
||||
HypERC20Config | HypERC20CollateralConfig |
||||
> = objMap(coreConfig, (key) => ({ |
||||
...coreConfig[key], |
||||
...tokenConfig, |
||||
owner: owner.address, |
||||
})); |
||||
|
||||
let erc20: ERC20 | undefined; |
||||
if (withCollateral) { |
||||
erc20 = await new ERC20Test__factory(owner).deploy( |
||||
tokenConfig.name, |
||||
tokenConfig.symbol, |
||||
tokenConfig.totalSupply, |
||||
); |
||||
configWithTokenInfo.test1 = { |
||||
...configWithTokenInfo.test1, |
||||
type: TokenType.collateral, |
||||
token: erc20.address, |
||||
}; |
||||
} |
||||
|
||||
deployer = new HypERC20Deployer(multiProvider, configWithTokenInfo, core); |
||||
contracts = await deployer.deploy(); |
||||
local = contracts[localChain].router as HypERC20; |
||||
|
||||
if (withCollateral) { |
||||
await erc20!.approve(local.address, amount); |
||||
} |
||||
|
||||
remote = contracts[remoteChain].router as HypERC20; |
||||
}); |
||||
|
||||
it('should not be initializable again', async () => { |
||||
const initializeTx = withCollateral |
||||
? (local as HypERC20Collateral).initialize( |
||||
ethers.constants.AddressZero, |
||||
ethers.constants.AddressZero, |
||||
) |
||||
: (local as HypERC20).initialize( |
||||
ethers.constants.AddressZero, |
||||
ethers.constants.AddressZero, |
||||
0, |
||||
'', |
||||
'', |
||||
); |
||||
await expect(initializeTx).to.be.revertedWith( |
||||
'Initializable: contract is already initialized', |
||||
); |
||||
}); |
||||
|
||||
it('should mint total supply to deployer', async () => { |
||||
await expectBalance(local, recipient, 0); |
||||
await expectBalance(local, owner, totalSupply); |
||||
await expectBalance(remote, recipient, 0); |
||||
await expectBalance(remote, owner, totalSupply); |
||||
}); |
||||
|
||||
// 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( |
||||
remoteDomain, |
||||
utils.addressToBytes32(recipient.address), |
||||
amount, |
||||
); |
||||
|
||||
await expectBalance(local, recipient, 0); |
||||
await expectBalance(local, owner, totalSupply - amount); |
||||
await expectBalance(remote, recipient, 0); |
||||
await expectBalance(remote, owner, totalSupply); |
||||
|
||||
await core.processMessages(); |
||||
|
||||
await expectBalance(local, recipient, 0); |
||||
await expectBalance(local, owner, totalSupply - amount); |
||||
await expectBalance(remote, recipient, amount); |
||||
await expectBalance(remote, owner, totalSupply); |
||||
}); |
||||
|
||||
it.skip('allows interchain gas payment for remote transfers', async () => { |
||||
const interchainGasPaymaster = |
||||
core.contractsMap[localChain].interchainGasPaymaster.contract; |
||||
await expect( |
||||
local.transferRemote( |
||||
remoteDomain, |
||||
utils.addressToBytes32(recipient.address), |
||||
amount, |
||||
{ |
||||
value: testInterchainGasPayment, |
||||
}, |
||||
), |
||||
).to.emit(interchainGasPaymaster, 'GasPayment'); |
||||
}); |
||||
|
||||
it('should prevent remote transfer of unowned balance', async () => { |
||||
const revertReason = withCollateral |
||||
? 'ERC20: insufficient allowance' |
||||
: 'ERC20: burn amount exceeds balance'; |
||||
await expect( |
||||
local |
||||
.connect(recipient) |
||||
.transferRemote( |
||||
remoteDomain, |
||||
utils.addressToBytes32(recipient.address), |
||||
amount, |
||||
), |
||||
).to.be.revertedWith(revertReason); |
||||
}); |
||||
|
||||
it('should emit TransferRemote events', async () => { |
||||
expect( |
||||
await local.transferRemote( |
||||
remoteDomain, |
||||
utils.addressToBytes32(recipient.address), |
||||
amount, |
||||
), |
||||
) |
||||
.to.emit(local, 'SentTransferRemote') |
||||
.withArgs(remoteDomain, recipient.address, amount); |
||||
expect(await core.processMessages()) |
||||
.to.emit(local, 'ReceivedTransferRemote') |
||||
.withArgs(localDomain, recipient.address, amount); |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
const expectBalance = async ( |
||||
token: HypERC20 | HypERC20Collateral | ERC20, |
||||
signer: SignerWithAddress, |
||||
balance: number, |
||||
) => { |
||||
if (Object.keys(token.interface.functions).includes('wrappedToken()')) { |
||||
const wrappedToken = await (token as HypERC20Collateral).wrappedToken(); |
||||
token = ERC20__factory.connect(wrappedToken, signer); |
||||
} |
||||
return expectTokenBalance(token as HypERC20, signer, balance); |
||||
}; |
||||
|
||||
const expectTokenBalance = async ( |
||||
token: ERC20, |
||||
signer: SignerWithAddress, |
||||
balance: number, |
||||
) => expect(await token.balanceOf(signer.address)).to.eq(balance); |
@ -1,294 +0,0 @@ |
||||
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; |
||||
import '@nomiclabs/hardhat-waffle'; |
||||
import { expect } from 'chai'; |
||||
import { ethers } from 'hardhat'; |
||||
|
||||
import { |
||||
ChainMap, |
||||
ChainNameToDomainId, |
||||
TestChainNames, |
||||
TestCoreApp, |
||||
TestCoreDeployer, |
||||
getTestMultiProvider, |
||||
objMap, |
||||
} from '@hyperlane-xyz/sdk'; |
||||
import { utils } from '@hyperlane-xyz/utils'; |
||||
|
||||
import { |
||||
HypERC721CollateralConfig, |
||||
HypERC721Config, |
||||
SyntheticConfig, |
||||
TokenType, |
||||
} from '../src/config'; |
||||
import { HypERC721Contracts } from '../src/contracts'; |
||||
import { HypERC721Deployer } from '../src/deploy'; |
||||
import { |
||||
ERC721, |
||||
ERC721Test__factory, |
||||
ERC721__factory, |
||||
HypERC721, |
||||
HypERC721Collateral, |
||||
HypERC721URICollateral, |
||||
HypERC721URIStorage, |
||||
} from '../src/types'; |
||||
|
||||
const localChain = 'test1'; |
||||
const remoteChain = 'test2'; |
||||
const localDomain = ChainNameToDomainId[localChain]; |
||||
const remoteDomain = ChainNameToDomainId[remoteChain]; |
||||
const totalSupply = 50; |
||||
const tokenId = 10; |
||||
const tokenId2 = 20; |
||||
const tokenId3 = 30; |
||||
const tokenId4 = 40; |
||||
const testInterchainGasPayment = 123456789; |
||||
|
||||
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, |
||||
}, |
||||
}; |
||||
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; |
||||
|
||||
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, |
||||
})); |
||||
|
||||
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, |
||||
}; |
||||
} |
||||
|
||||
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); |
||||
} |
||||
|
||||
remote = contracts[remoteChain].router; |
||||
}); |
||||
|
||||
it('should not be initializable again', async () => { |
||||
const initializeTx = withCollateral |
||||
? (local as HypERC721Collateral).initialize( |
||||
ethers.constants.AddressZero, |
||||
ethers.constants.AddressZero, |
||||
) |
||||
: (local as HypERC721).initialize( |
||||
ethers.constants.AddressZero, |
||||
ethers.constants.AddressZero, |
||||
0, |
||||
'', |
||||
'', |
||||
); |
||||
await expect(initializeTx).to.be.revertedWith( |
||||
'Initializable: contract is already initialized', |
||||
); |
||||
}); |
||||
|
||||
it('should mint total supply to deployer on local domain', async () => { |
||||
await expectBalance(local, recipient, 0); |
||||
await expectBalance(local, owner, totalSupply); |
||||
await expectBalance(remote, recipient, 0); |
||||
await expectBalance(remote, owner, 0); |
||||
}); |
||||
|
||||
// do not test underlying ERC721 collateral functionality
|
||||
if (!withCollateral) { |
||||
it('should allow for local transfers', async () => { |
||||
await (local as HypERC721).transferFrom( |
||||
owner.address, |
||||
recipient.address, |
||||
tokenId, |
||||
); |
||||
await expectBalance(local, recipient, 1); |
||||
await expectBalance(local, owner, totalSupply - 1); |
||||
await expectBalance(remote, recipient, 0); |
||||
await expectBalance(remote, owner, 0); |
||||
}); |
||||
} |
||||
|
||||
it('should not allow transfers of nonexistent identifiers', async () => { |
||||
const invalidTokenId = totalSupply + 10; |
||||
if (!withCollateral) { |
||||
await expect( |
||||
(local as HypERC721).transferFrom( |
||||
owner.address, |
||||
recipient.address, |
||||
invalidTokenId, |
||||
), |
||||
).to.be.revertedWith('ERC721: invalid token ID'); |
||||
} |
||||
await expect( |
||||
local.transferRemote( |
||||
remoteDomain, |
||||
utils.addressToBytes32(recipient.address), |
||||
invalidTokenId, |
||||
), |
||||
).to.be.revertedWith('ERC721: invalid token ID'); |
||||
}); |
||||
|
||||
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 core.processMessages(); |
||||
|
||||
await expectBalance(local, recipient, 0); |
||||
await expectBalance(local, owner, totalSupply - 1); |
||||
await expectBalance(remote, recipient, 1); |
||||
await expectBalance(remote, owner, 0); |
||||
}); |
||||
|
||||
if (withUri && withCollateral) { |
||||
it('should relay URI with remote transfer', async () => { |
||||
const remoteUri = remote as HypERC721URIStorage; |
||||
await expect(remoteUri.tokenURI(tokenId2)).to.be.revertedWith(''); |
||||
|
||||
await local.transferRemote( |
||||
remoteDomain, |
||||
utils.addressToBytes32(recipient.address), |
||||
tokenId2, |
||||
); |
||||
|
||||
await expect(remoteUri.tokenURI(tokenId2)).to.be.revertedWith(''); |
||||
|
||||
await core.processMessages(); |
||||
|
||||
expect(await remoteUri.tokenURI(tokenId2)).to.equal( |
||||
`TEST-BASE-URI${tokenId2}`, |
||||
); |
||||
}); |
||||
} |
||||
|
||||
it('should prevent remote transfer of unowned id', async () => { |
||||
const revertReason = withCollateral |
||||
? 'ERC721: transfer from incorrect owner' |
||||
: '!owner'; |
||||
await expect( |
||||
local |
||||
.connect(recipient) |
||||
.transferRemote( |
||||
remoteDomain, |
||||
utils.addressToBytes32(recipient.address), |
||||
tokenId2, |
||||
), |
||||
).to.be.revertedWith(revertReason); |
||||
}); |
||||
|
||||
it.skip('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 ( |
||||
token: HypERC721 | HypERC721Collateral | ERC721, |
||||
signer: SignerWithAddress, |
||||
balance: number, |
||||
) => { |
||||
if (Object.keys(token.interface.functions).includes('wrappedToken()')) { |
||||
const wrappedToken = await (token as HypERC721Collateral).wrappedToken(); |
||||
token = ERC721__factory.connect(wrappedToken, signer); |
||||
} |
||||
return expectTokenBalance(token as HypERC721, signer, balance); |
||||
}; |
||||
|
||||
const expectTokenBalance = async ( |
||||
token: ERC721, |
||||
signer: SignerWithAddress, |
||||
balance: number, |
||||
) => expect(await token.balanceOf(signer.address)).to.eq(balance); |
@ -1,36 +0,0 @@ |
||||
{ |
||||
"compilerOptions": { |
||||
"declaration": true, |
||||
"declarationMap": true, |
||||
"esModuleInterop": true, |
||||
"forceConsistentCasingInFileNames": true, |
||||
"incremental": false, |
||||
"lib": ["es2015", "es5", "dom"], |
||||
"module": "commonjs", |
||||
"moduleResolution": "node", |
||||
"noEmitOnError": true, |
||||
"noFallthroughCasesInSwitch": true, |
||||
"noImplicitAny": false, |
||||
"noImplicitReturns": true, |
||||
"noUnusedLocals": true, |
||||
"preserveSymlinks": true, |
||||
"preserveWatchOutput": true, |
||||
"pretty": false, |
||||
"sourceMap": true, |
||||
"target": "es6", |
||||
"strict": true, |
||||
"outDir": "./dist", |
||||
"rootDir": "./", |
||||
"resolveJsonModule": true |
||||
}, |
||||
"exclude": [ |
||||
"./node_modules/", |
||||
"./dist/", |
||||
"./types/hardhat.d.ts" |
||||
], |
||||
"include": [ |
||||
"./src", |
||||
"./test", |
||||
], |
||||
"files": ["hardhat.config.ts"] |
||||
} |
Loading…
Reference in new issue