added tokenbridge code
This commit is contained in:
parent
6af7b63a24
commit
5541f5f2a9
|
@ -1,3 +1,6 @@
|
||||||
[submodule "projects/messenger/chains/evm/lib/forge-std"]
|
[submodule "projects/messenger/chains/evm/lib/forge-std"]
|
||||||
path = projects/messenger/chains/evm/lib/forge-std
|
path = projects/messenger/chains/evm/lib/forge-std
|
||||||
url = https://github.com/foundry-rs/forge-std
|
url = https://github.com/foundry-rs/forge-std
|
||||||
|
[submodule "projects/evm-tokenbridge/chains/evm/lib/openzeppelin-contracts"]
|
||||||
|
path = projects/evm-tokenbridge/chains/evm/lib/openzeppelin-contracts
|
||||||
|
url = https://github.com/openzeppelin/openzeppelin-contracts
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
node_modules/
|
||||||
|
wormhole/
|
||||||
|
deployment.json
|
|
@ -0,0 +1,25 @@
|
||||||
|
# EVM Messenger
|
||||||
|
Simple messenger project that sends a "Hello World" message between two EVM chains using Wormhole.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
This project uses Foundry to compile and deploy EVM contracts. You can find install instructions at [`https://getfoundry.sh`](http://getfoundry.sh)
|
||||||
|
|
||||||
|
The javascript dependencies can be installed via `npm install` in this folder.
|
||||||
|
|
||||||
|
You will also need Docker; you can get either [Docker Desktop](https://docs.docker.com/get-docker/) if you're developing on your computer or if you're in a headless vm, install [Docker Engine](https://docs.docker.com/engine/)
|
||||||
|
|
||||||
|
## Run Guardiand
|
||||||
|
After you have the dependencies installed, we'll need to spin up the EVM chains, deploy the Wormhole contracts to them, then startup a Wormhole Guardian to observe and sign VAAs. We have provided a script to automate this all for you.
|
||||||
|
|
||||||
|
Simply run `npm run guardiand` and wait while the Wormhole Guardian builds a docker image. The first time you run this command, it might take a while (up to 550 seconds on a modern laptop!). After the image is built however, it'll be relatively fast to bring it up and down.
|
||||||
|
|
||||||
|
## Test Scripts
|
||||||
|
After you have Guardiand running, you can run the basic test with `npm run test`. This will:
|
||||||
|
- Deploy a simple Messenger contract (found in chains/evm/src/Messenger.sol) to each EVM chain
|
||||||
|
- Register each contract with the other chain
|
||||||
|
- Send a message from each contract
|
||||||
|
- Fetch the VAA from the Guardian
|
||||||
|
- Submit the VAA to each contract
|
||||||
|
- Print out the Message
|
||||||
|
|
||||||
|
If everything goes correctly, you should get a printout with the Hello World messages on each chain.
|
|
@ -0,0 +1,3 @@
|
||||||
|
cache/
|
||||||
|
out/
|
||||||
|
!src/Wormhole/
|
|
@ -0,0 +1,7 @@
|
||||||
|
[default]
|
||||||
|
src = 'src'
|
||||||
|
out = 'out'
|
||||||
|
libs = ['lib']
|
||||||
|
solc_version = '0.8.10'
|
||||||
|
|
||||||
|
# See more config options https://github.com/foundry-rs/foundry/tree/master/config
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit e734b42fc2245b520372bca0099870f40f1e6f38
|
|
@ -0,0 +1 @@
|
||||||
|
@openzeppelin/=lib/openzeppelin-contracts
|
|
@ -0,0 +1,53 @@
|
||||||
|
//SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
//If the below line shows an error, ignore it, it's cause you're root folder is not chains/evm.
|
||||||
|
import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol";
|
||||||
|
import "./Wormhole/ITokenBridge.sol";
|
||||||
|
import "./Wormhole/PortalWrappedToken.sol";
|
||||||
|
|
||||||
|
contract Treasury {
|
||||||
|
|
||||||
|
address private token_bridge_address = address(0x0290FB167208Af455bB137780163b7B7a9a10C16);
|
||||||
|
ITokenBridge token_bridge = ITokenBridge(token_bridge_address);
|
||||||
|
address private TKN_address = address(0x2D8BE6BF0baA74e0A907016679CaE9190e80dD0A);
|
||||||
|
ERC20PresetMinterPauser TKN = ERC20PresetMinterPauser(TKN_address);
|
||||||
|
|
||||||
|
uint32 nonce = 0;
|
||||||
|
mapping(uint16 => bytes32) _applicationContracts;
|
||||||
|
mapping(bytes32 => bool) _completedMessages;
|
||||||
|
|
||||||
|
address owner;
|
||||||
|
|
||||||
|
constructor(){
|
||||||
|
owner = msg.sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Registers it's sibling applications on other chains as the only ones that can send this instance messages
|
||||||
|
*/
|
||||||
|
function registerApplicationContracts(uint16 chainId, bytes32 applicationAddr) public {
|
||||||
|
require(msg.sender == owner, "Only owner can register new chains!");
|
||||||
|
_applicationContracts[chainId] = applicationAddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Returns the Balance of this Contract
|
||||||
|
function getTKNCount() public view returns (uint256) {
|
||||||
|
return TKN.balanceOf(address(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Returns the Balance of Wrapped Count
|
||||||
|
function getWrappedCount(PortalWrappedToken wrappedToken) public view returns (uint256) {
|
||||||
|
return wrappedToken.balanceOf(address(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
function bridgeToken(uint256 amt, uint16 receipientChainId, bytes32 recipient) public returns (uint64 sequence) {
|
||||||
|
nonce += 1;
|
||||||
|
return token_bridge.transferTokens(TKN_address, amt, receipientChainId, recipient, 0, nonce);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function approveTokenBridge(uint256 amt) public returns (bool) {
|
||||||
|
return TKN.approve(token_bridge_address, amt);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
// contracts/Getters.sol
|
||||||
|
// SPDX-License-Identifier: Apache 2
|
||||||
|
|
||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
interface BridgeGetters {
|
||||||
|
function governanceActionIsConsumed(bytes32 hash) external view returns (bool) ;
|
||||||
|
function isInitialized(address impl) external view returns (bool) ;
|
||||||
|
function isTransferCompleted(bytes32 hash) external view returns (bool) ;
|
||||||
|
function chainId() external view returns (uint16);
|
||||||
|
function governanceChainId() external view returns (uint16);
|
||||||
|
function governanceContract() external view returns (bytes32);
|
||||||
|
function wrappedAsset(uint16 tokenChainId, bytes32 tokenAddress) external view returns (address);
|
||||||
|
function bridgeContracts(uint16 chainId_) external view returns (bytes32);
|
||||||
|
function tokenImplementation() external view returns (address);
|
||||||
|
function outstandingBridged(address token) external view returns (uint256);
|
||||||
|
function isWrappedAsset(address token) external view returns (bool);
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
// contracts/Bridge.sol
|
||||||
|
// SPDX-License-Identifier: Apache 2
|
||||||
|
|
||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
import "./BridgeGetters.sol";
|
||||||
|
|
||||||
|
interface ITokenBridge is BridgeGetters {
|
||||||
|
/*
|
||||||
|
* @dev Produce a AssetMeta message for a given token
|
||||||
|
*/
|
||||||
|
function attestToken(address tokenAddress, uint32 nonce) external payable returns (uint64 sequence);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @notice Send eth through portal by first wrapping it to WETH.
|
||||||
|
*/
|
||||||
|
function wrapAndTransferETH(
|
||||||
|
uint16 recipientChain,
|
||||||
|
bytes32 recipient,
|
||||||
|
uint256 arbiterFee,
|
||||||
|
uint32 nonce
|
||||||
|
) external payable returns (uint64 sequence);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @notice Send eth through portal by first wrapping it.
|
||||||
|
*
|
||||||
|
* @dev This type of transfer is called a "contract-controlled transfer".
|
||||||
|
* There are three differences from a regular token transfer:
|
||||||
|
* 1) Additional arbitrary payload can be attached to the message
|
||||||
|
* 2) Only the recipient (typically a contract) can redeem the transaction
|
||||||
|
* 3) The sender's address (msg.sender) is also included in the transaction payload
|
||||||
|
*
|
||||||
|
* With these three additional components, xDapps can implement cross-chain
|
||||||
|
* composable interactions.
|
||||||
|
*/
|
||||||
|
function wrapAndTransferETHWithPayload(
|
||||||
|
uint16 recipientChain,
|
||||||
|
bytes32 recipient,
|
||||||
|
uint32 nonce,
|
||||||
|
bytes memory payload
|
||||||
|
) external payable returns (uint64 sequence);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @notice Send ERC20 token through portal.
|
||||||
|
*/
|
||||||
|
function transferTokens(
|
||||||
|
address token,
|
||||||
|
uint256 amount,
|
||||||
|
uint16 recipientChain,
|
||||||
|
bytes32 recipient,
|
||||||
|
uint256 arbiterFee,
|
||||||
|
uint32 nonce
|
||||||
|
) external payable returns (uint64 sequence);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @notice Send ERC20 token through portal.
|
||||||
|
*
|
||||||
|
* @dev This type of transfer is called a "contract-controlled transfer".
|
||||||
|
* There are three differences from a regular token transfer:
|
||||||
|
* 1) Additional arbitrary payload can be attached to the message
|
||||||
|
* 2) Only the recipient (typically a contract) can redeem the transaction
|
||||||
|
* 3) The sender's address (msg.sender) is also included in the transaction payload
|
||||||
|
*
|
||||||
|
* With these three additional components, xDapps can implement cross-chain
|
||||||
|
* composable interactions.
|
||||||
|
*/
|
||||||
|
function transferTokensWithPayload(
|
||||||
|
address token,
|
||||||
|
uint256 amount,
|
||||||
|
uint16 recipientChain,
|
||||||
|
bytes32 recipient,
|
||||||
|
uint32 nonce,
|
||||||
|
bytes memory payload
|
||||||
|
) external payable returns (uint64 sequence);
|
||||||
|
|
||||||
|
function updateWrapped(bytes memory encodedVm) external returns (address token);
|
||||||
|
|
||||||
|
function createWrapped(bytes memory encodedVm) external returns (address token);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @notice Complete a contract-controlled transfer of an ERC20 token.
|
||||||
|
*
|
||||||
|
* @dev The transaction can only be redeemed by the recipient, typically a
|
||||||
|
* contract.
|
||||||
|
*
|
||||||
|
* @param encodedVm A byte array containing a VAA signed by the guardians.
|
||||||
|
*
|
||||||
|
* @return The byte array representing a BridgeStructs.TransferWithPayload.
|
||||||
|
*/
|
||||||
|
function completeTransferWithPayload(bytes memory encodedVm) external returns (bytes memory);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @notice Complete a contract-controlled transfer of WETH, and unwrap to ETH.
|
||||||
|
*
|
||||||
|
* @dev The transaction can only be redeemed by the recipient, typically a
|
||||||
|
* contract.
|
||||||
|
*
|
||||||
|
* @param encodedVm A byte array containing a VAA signed by the guardians.
|
||||||
|
*
|
||||||
|
* @return The byte array representing a BridgeStructs.TransferWithPayload.
|
||||||
|
*/
|
||||||
|
function completeTransferAndUnwrapETHWithPayload(bytes memory encodedVm) external returns (bytes memory);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @notice Complete a transfer of an ERC20 token.
|
||||||
|
*
|
||||||
|
* @dev The msg.sender gets paid the associated fee.
|
||||||
|
*
|
||||||
|
* @param encodedVm A byte array containing a VAA signed by the guardians.
|
||||||
|
*/
|
||||||
|
function completeTransfer(bytes memory encodedVm) external ;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @notice Complete a transfer of WETH and unwrap to eth.
|
||||||
|
*
|
||||||
|
* @dev The msg.sender gets paid the associated fee.
|
||||||
|
*
|
||||||
|
* @param encodedVm A byte array containing a VAA signed by the guardians.
|
||||||
|
*/
|
||||||
|
function completeTransferAndUnwrapETH(bytes memory encodedVm) external ;
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
// contracts/TokenImplementation.sol
|
||||||
|
// SPDX-License-Identifier: Apache 2
|
||||||
|
|
||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
|
||||||
|
// Based on the OpenZepplin ERC20 implementation, licensed under MIT
|
||||||
|
interface PortalWrappedToken {
|
||||||
|
event Transfer(address indexed from, address indexed to, uint256 value);
|
||||||
|
event Approval(address indexed owner, address indexed spender, uint256 value);
|
||||||
|
|
||||||
|
function name() external view returns (string memory);
|
||||||
|
|
||||||
|
function symbol() external view returns (string memory);
|
||||||
|
|
||||||
|
function owner() external view returns (address);
|
||||||
|
|
||||||
|
function decimals() external view returns (uint8);
|
||||||
|
|
||||||
|
function totalSupply() external view returns (uint256);
|
||||||
|
|
||||||
|
function chainId() external view returns (uint16);
|
||||||
|
|
||||||
|
function nativeContract() external view returns (bytes32) ;
|
||||||
|
|
||||||
|
function balanceOf(address account_) external view returns (uint256) ;
|
||||||
|
|
||||||
|
function transfer(address recipient_, uint256 amount_) external returns (bool) ;
|
||||||
|
|
||||||
|
function allowance(address owner_, address spender_) external view returns (uint256) ;
|
||||||
|
|
||||||
|
function approve(address spender_, uint256 amount_) external returns (bool) ;
|
||||||
|
|
||||||
|
function transferFrom(address sender_, address recipient_, uint256 amount_) external returns (bool) ;
|
||||||
|
|
||||||
|
function increaseAllowance(address spender_, uint256 addedValue_) external returns (bool) ;
|
||||||
|
|
||||||
|
function decreaseAllowance(address spender_, uint256 subtractedValue_) external returns (bool) ;
|
||||||
|
|
||||||
|
function mint(address account_, uint256 amount_) external ;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
// contracts/Structs.sol
|
||||||
|
// SPDX-License-Identifier: Apache 2
|
||||||
|
|
||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
interface Structs {
|
||||||
|
struct Provider {
|
||||||
|
uint16 chainId;
|
||||||
|
uint16 governanceChainId;
|
||||||
|
bytes32 governanceContract;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GuardianSet {
|
||||||
|
address[] keys;
|
||||||
|
uint32 expirationTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Signature {
|
||||||
|
bytes32 r;
|
||||||
|
bytes32 s;
|
||||||
|
uint8 v;
|
||||||
|
uint8 guardianIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VM {
|
||||||
|
uint8 version;
|
||||||
|
uint32 timestamp;
|
||||||
|
uint32 nonce;
|
||||||
|
uint16 emitterChainId;
|
||||||
|
bytes32 emitterAddress;
|
||||||
|
uint64 sequence;
|
||||||
|
uint8 consistencyLevel;
|
||||||
|
bytes payload;
|
||||||
|
|
||||||
|
uint32 guardianSetIndex;
|
||||||
|
Signature[] signatures;
|
||||||
|
|
||||||
|
bytes32 hash;
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"name": "xdapp-starter",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "A simple template for getting started with xDapps.",
|
||||||
|
"main": "starter.js",
|
||||||
|
"scripts": {
|
||||||
|
"guardiand": "sh wormhole.sh",
|
||||||
|
"cleanup": "docker kill guardiand && docker rm guardiand && npx pm2 kill",
|
||||||
|
"test": "sh tests/treasury_bridge.sh"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"workspaces": [],
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@certusone/wormhole-sdk": "^0.3.3",
|
||||||
|
"byteify": "^2.0.10",
|
||||||
|
"ethers": "^5.6.9",
|
||||||
|
"ganache": "^7.3.1",
|
||||||
|
"node-fetch": "^3.2.6",
|
||||||
|
"pm2": "^5.2.0"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
node treasury.js evm0 deploy
|
||||||
|
node treasury.js evm1 deploy
|
||||||
|
node treasury.js evm0 register_chain evm1
|
||||||
|
node treasury.js evm1 register_chain evm0
|
||||||
|
|
||||||
|
node treasury.js evm0 get_tokens 100
|
||||||
|
node treasury.js evm0 attest_token evm1
|
||||||
|
node treasury.js evm1 get_token_counts
|
||||||
|
node treasury.js evm0 bridge_token evm1 50
|
||||||
|
node treasury.js evm0 get_token_counts
|
||||||
|
node treasury.js evm1 get_token_counts
|
|
@ -0,0 +1,334 @@
|
||||||
|
import { exec } from "child_process";
|
||||||
|
import fs from "fs";
|
||||||
|
import { ethers } from "ethers";
|
||||||
|
import {
|
||||||
|
getEmitterAddressEth,
|
||||||
|
parseSequenceFromLogEth,
|
||||||
|
attestFromEth,
|
||||||
|
tryNativeToHexString,
|
||||||
|
} from "@certusone/wormhole-sdk";
|
||||||
|
|
||||||
|
import fetch from "node-fetch";
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
let config = JSON.parse(fs.readFileSync("./xdapp.config.json").toString());
|
||||||
|
let network = config.networks[process.argv[2]];
|
||||||
|
|
||||||
|
let deployment;
|
||||||
|
try {
|
||||||
|
deployment = JSON.parse(fs.readFileSync("./deployment.json").toString());
|
||||||
|
} catch (e) {
|
||||||
|
deployment = {};
|
||||||
|
deployment[[process.argv[2]]] = {
|
||||||
|
deployedAddress: "",
|
||||||
|
emittedVAAs: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!network) {
|
||||||
|
throw new Error("Network not defined in config file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.argv[3] == "deploy") {
|
||||||
|
console.log(
|
||||||
|
`Deploying EVM network: ${process.argv[2]} to ${network.rpc}`
|
||||||
|
);
|
||||||
|
|
||||||
|
exec(
|
||||||
|
`cd chains/evm && forge build && forge create --legacy --rpc-url ${network.rpc} --private-key ${network.privateKey} src/Treasury.sol:Treasury && exit`,
|
||||||
|
(err, out, errStr) => {
|
||||||
|
if (err) {
|
||||||
|
throw new Error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out) {
|
||||||
|
console.log(out);
|
||||||
|
deployment[[process.argv[2]]] = {
|
||||||
|
deployedAddress: "",
|
||||||
|
emittedVAAs: []
|
||||||
|
};
|
||||||
|
deployment[process.argv[2]].deployedAddress = out
|
||||||
|
.split("Deployed to: ")[1]
|
||||||
|
.split("\n")[0]
|
||||||
|
.trim();
|
||||||
|
deployment[process.argv[2]].emittedVAAs = [];
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
"./deployment.json",
|
||||||
|
JSON.stringify(deployment, null, 4)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else if (process.argv[3] == "register_chain") {
|
||||||
|
if (!deployment[process.argv[2]].deployedAddress) {
|
||||||
|
throw new Error("Deploy to this network first!");
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetNetwork = config.networks[process.argv[4]];
|
||||||
|
const targetDeployment = deployment[process.argv[4]]
|
||||||
|
if (!targetDeployment.deployedAddress) {
|
||||||
|
throw new Error("Target Network not deployed yet!");
|
||||||
|
}
|
||||||
|
|
||||||
|
let emitterAddr = Buffer.from(
|
||||||
|
getEmitterAddressEth(targetDeployment.deployedAddress),
|
||||||
|
"hex"
|
||||||
|
);
|
||||||
|
|
||||||
|
const signer = new ethers.Wallet(network.privateKey).connect(
|
||||||
|
new ethers.providers.JsonRpcProvider(network.rpc)
|
||||||
|
);
|
||||||
|
|
||||||
|
const treasury = new ethers.Contract(
|
||||||
|
deployment[process.argv[2]].deployedAddress,
|
||||||
|
JSON.parse(
|
||||||
|
fs
|
||||||
|
.readFileSync(
|
||||||
|
"./chains/evm/out/Treasury.sol/Treasury.json"
|
||||||
|
)
|
||||||
|
.toString()
|
||||||
|
).abi,
|
||||||
|
signer
|
||||||
|
);
|
||||||
|
await treasury.registerApplicationContracts(
|
||||||
|
targetNetwork.wormholeChainId,
|
||||||
|
emitterAddr
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`Network(${process.argv[2]}) Registered Emitter: ${targetDeployment.deployedAddress} from Chain: ${process.argv[4]}`
|
||||||
|
);
|
||||||
|
} else if (process.argv[3] == "get_tokens") {
|
||||||
|
if (!deployment[process.argv[2]].deployedAddress) {
|
||||||
|
throw new Error("Deploy to this network first!");
|
||||||
|
}
|
||||||
|
|
||||||
|
const signer = new ethers.Wallet(network.privateKey).connect(
|
||||||
|
new ethers.providers.JsonRpcProvider(network.rpc)
|
||||||
|
);
|
||||||
|
const treasury = new ethers.Contract(
|
||||||
|
deployment[process.argv[2]].deployedAddress,
|
||||||
|
JSON.parse(
|
||||||
|
fs
|
||||||
|
.readFileSync(
|
||||||
|
"./chains/evm/out/Treasury.sol/Treasury.json"
|
||||||
|
)
|
||||||
|
.toString()
|
||||||
|
).abi,
|
||||||
|
signer
|
||||||
|
);
|
||||||
|
|
||||||
|
const TKN = new ethers.Contract(
|
||||||
|
network.testToken,
|
||||||
|
JSON.parse(
|
||||||
|
fs
|
||||||
|
.readFileSync(
|
||||||
|
"./chains/evm/out/ERC20PresetMinterPauser.sol/ERC20PresetMinterPauser.json"
|
||||||
|
)
|
||||||
|
.toString()
|
||||||
|
).abi,
|
||||||
|
signer
|
||||||
|
);
|
||||||
|
console.log(`${process.argv[2]} Treasury has ${await treasury.getTKNCount()} tokens.`);
|
||||||
|
console.log(`Minting 100 tokens.`);
|
||||||
|
await TKN.mint(deployment[process.argv[2]].deployedAddress, 100);
|
||||||
|
await new Promise((r) => setTimeout(r, 3000));
|
||||||
|
console.log(`${process.argv[2]} Treasury has ${await treasury.getTKNCount()} tokens.`);
|
||||||
|
} else if (process.argv[3] == "attest_token") {
|
||||||
|
if (!deployment[process.argv[2]].deployedAddress) {
|
||||||
|
throw new Error("Deploy to this network first!");
|
||||||
|
}
|
||||||
|
|
||||||
|
const signer = new ethers.Wallet(network.privateKey).connect(
|
||||||
|
new ethers.providers.JsonRpcProvider(network.rpc)
|
||||||
|
);
|
||||||
|
|
||||||
|
const networkTokenAttestation = await attestFromEth(
|
||||||
|
network.tokenBridgeAddress,
|
||||||
|
signer,
|
||||||
|
network.testToken
|
||||||
|
);
|
||||||
|
|
||||||
|
const emitterAddr = getEmitterAddressEth(network.tokenBridgeAddress);
|
||||||
|
const seq = parseSequenceFromLogEth(networkTokenAttestation, network.bridgeAddress);
|
||||||
|
const vaaURL = `${config.wormhole.restAddress}/v1/signed_vaa/${network.wormholeChainId}/${emitterAddr}/${seq}`;
|
||||||
|
console.log("Searching for: ", vaaURL);
|
||||||
|
let vaaBytes = await (await fetch(vaaURL)).json();
|
||||||
|
while(!vaaBytes.vaaBytes){
|
||||||
|
console.log("VAA not found, retrying in 5s!");
|
||||||
|
await new Promise((r) => setTimeout(r, 5000)); //Timeout to let Guardiand pick up log and have VAA ready
|
||||||
|
vaaBytes = await (await fetch(vaaURL)).json();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!deployment[process.argv[2]].emittedVAAs) {
|
||||||
|
deployment[process.argv[2]].emittedVAAs = [vaaBytes.vaaBytes];
|
||||||
|
} else {
|
||||||
|
deployment[process.argv[2]].emittedVAAs.push(vaaBytes.vaaBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
"./deployment.json",
|
||||||
|
JSON.stringify(deployment, null, 2)
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`Network(${process.argv[2]}) Emitted VAA: `,
|
||||||
|
vaaBytes.vaaBytes
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now create the Wrapped Version of the Token on the target chain
|
||||||
|
const targetNetwork = config.networks[process.argv[4]];
|
||||||
|
const targetDeployment = deployment[process.argv[4]];
|
||||||
|
const targetSigner = new ethers.Wallet(network.privateKey).connect(
|
||||||
|
new ethers.providers.JsonRpcProvider(targetNetwork.rpc)
|
||||||
|
);
|
||||||
|
const targetTokenBridge = new ethers.Contract(
|
||||||
|
targetNetwork.tokenBridgeAddress,
|
||||||
|
JSON.parse(
|
||||||
|
fs
|
||||||
|
.readFileSync(
|
||||||
|
"./chains/evm/out/ITokenBridge.sol/ITokenBridge.json"
|
||||||
|
)
|
||||||
|
.toString()
|
||||||
|
).abi,
|
||||||
|
targetSigner
|
||||||
|
);
|
||||||
|
|
||||||
|
await targetTokenBridge.createWrapped(Buffer.from(vaaBytes.vaaBytes, "base64"), {
|
||||||
|
gasLimit: 2000000
|
||||||
|
})
|
||||||
|
await new Promise((r) => setTimeout(r, 5000)); //Time out to let block propogate
|
||||||
|
const wrappedTokenAddress = await targetTokenBridge.wrappedAsset(network.wormholeChainId, Buffer.from(tryNativeToHexString(network.testToken, "ethereum"), "hex"));
|
||||||
|
console.log("Wrapped token created at: ", wrappedTokenAddress);
|
||||||
|
targetDeployment["wrappedTestTokenAddress"] = wrappedTokenAddress;
|
||||||
|
|
||||||
|
deployment[process.argv[4]] = targetDeployment;
|
||||||
|
fs.writeFileSync('./deployment.json', JSON.stringify(deployment, null, 4));
|
||||||
|
} else if (process.argv[3] == "get_token_counts") {
|
||||||
|
if (!deployment[process.argv[2]].deployedAddress) {
|
||||||
|
throw new Error("Deploy to this network first!");
|
||||||
|
}
|
||||||
|
|
||||||
|
const signer = new ethers.Wallet(network.privateKey).connect(
|
||||||
|
new ethers.providers.JsonRpcProvider(network.rpc)
|
||||||
|
);
|
||||||
|
|
||||||
|
const treasury = new ethers.Contract(
|
||||||
|
deployment[process.argv[2]].deployedAddress,
|
||||||
|
JSON.parse(
|
||||||
|
fs
|
||||||
|
.readFileSync(
|
||||||
|
"./chains/evm/out/Treasury.sol/Treasury.json"
|
||||||
|
)
|
||||||
|
.toString()
|
||||||
|
).abi,
|
||||||
|
signer
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`${process.argv[2]} Treasury has ${await treasury.getTKNCount()} native TKN.`)
|
||||||
|
if(deployment[process.argv[2]]['wrappedTestTokenAddress']){
|
||||||
|
console.log(`${process.argv[2]} Treasury has ${await treasury.getWrappedCount(deployment[process.argv[2]]['wrappedTestTokenAddress'])} wrapped TKN.`)
|
||||||
|
}
|
||||||
|
} else if (process.argv[3] == "bridge_token") {
|
||||||
|
if (!deployment[process.argv[2]].deployedAddress) {
|
||||||
|
throw new Error("Deploy to this network first!");
|
||||||
|
}
|
||||||
|
|
||||||
|
const signer = new ethers.Wallet(network.privateKey).connect(
|
||||||
|
new ethers.providers.JsonRpcProvider(network.rpc)
|
||||||
|
);
|
||||||
|
|
||||||
|
const treasury = new ethers.Contract(
|
||||||
|
deployment[process.argv[2]].deployedAddress,
|
||||||
|
JSON.parse(
|
||||||
|
fs
|
||||||
|
.readFileSync(
|
||||||
|
"./chains/evm/out/Treasury.sol/Treasury.json"
|
||||||
|
)
|
||||||
|
.toString()
|
||||||
|
).abi,
|
||||||
|
signer
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// Remember to allow Token Bridge to move tokens from Treasury account to it's own account
|
||||||
|
console.log(`Approving ${process.argv[5]} Tokens to be bridged by Token Bridge`);
|
||||||
|
await treasury.approveTokenBridge(parseInt(process.argv[5], {
|
||||||
|
gasLimit: 2000000,
|
||||||
|
}));
|
||||||
|
await new Promise((r) => setTimeout(r, 5000)); //Time out to let block propogate
|
||||||
|
|
||||||
|
const targetNetwork = config.networks[process.argv[4]];
|
||||||
|
const targetDeployment = deployment[process.argv[4]]
|
||||||
|
if (!targetDeployment.deployedAddress) {
|
||||||
|
throw new Error("Target Network not deployed yet!");
|
||||||
|
}
|
||||||
|
console.log("Bridging Tokens!")
|
||||||
|
const targetRecepient = Buffer.from(tryNativeToHexString(targetDeployment.deployedAddress, "ethereum"), 'hex');
|
||||||
|
const tx = await (await treasury.bridgeToken(parseInt(process.argv[5]), targetNetwork.wormholeChainId, targetRecepient)).wait();
|
||||||
|
const emitterAddr = getEmitterAddressEth(network.tokenBridgeAddress);
|
||||||
|
const seq = parseSequenceFromLogEth(tx, network.bridgeAddress);
|
||||||
|
const vaaURL = `${config.wormhole.restAddress}/v1/signed_vaa/${network.wormholeChainId}/${emitterAddr}/${seq}`;
|
||||||
|
let vaaBytes = await (await fetch(vaaURL)).json();
|
||||||
|
while(!vaaBytes.vaaBytes){
|
||||||
|
console.log("VAA not found, retrying in 5s!");
|
||||||
|
await new Promise((r) => setTimeout(r, 5000)); //Timeout to let Guardiand pick up log and have VAA ready
|
||||||
|
vaaBytes = await (await fetch(vaaURL)).json();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!deployment[process.argv[2]].emittedVAAs) {
|
||||||
|
deployment[process.argv[2]].emittedVAAs = [vaaBytes.vaaBytes];
|
||||||
|
} else {
|
||||||
|
deployment[process.argv[2]].emittedVAAs.push(vaaBytes.vaaBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
"./deployment.json",
|
||||||
|
JSON.stringify(deployment, null, 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Network(${process.argv[2]}) Emitted VAA: `,
|
||||||
|
vaaBytes.vaaBytes
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now create the Wrapped Version of the Token on the target chain
|
||||||
|
const targetSigner = new ethers.Wallet(network.privateKey).connect(
|
||||||
|
new ethers.providers.JsonRpcProvider(targetNetwork.rpc)
|
||||||
|
);
|
||||||
|
const targetTokenBridge = new ethers.Contract(
|
||||||
|
targetNetwork.tokenBridgeAddress,
|
||||||
|
JSON.parse(
|
||||||
|
fs
|
||||||
|
.readFileSync(
|
||||||
|
"./chains/evm/out/ITokenBridge.sol/ITokenBridge.json"
|
||||||
|
)
|
||||||
|
.toString()
|
||||||
|
).abi,
|
||||||
|
targetSigner
|
||||||
|
);
|
||||||
|
const completeTransferTx = await targetTokenBridge.completeTransfer(Buffer.from(vaaBytes.vaaBytes, "base64"));
|
||||||
|
console.log("Complete Transfer TX: ", await completeTransferTx.wait());
|
||||||
|
} else if (process.argv[3] == "debug") {
|
||||||
|
const signer = new ethers.Wallet(network.privateKey).connect(
|
||||||
|
new ethers.providers.JsonRpcProvider(network.rpc)
|
||||||
|
);
|
||||||
|
|
||||||
|
const TKN = new ethers.Contract(
|
||||||
|
network.testToken,
|
||||||
|
JSON.parse(
|
||||||
|
fs
|
||||||
|
.readFileSync(
|
||||||
|
"./chains/evm/out/ERC20PresetMinterPauser.sol/ERC20PresetMinterPauser.json"
|
||||||
|
)
|
||||||
|
.toString()
|
||||||
|
).abi,
|
||||||
|
signer
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log((await TKN.allowance(deployment[process.argv[2]].deployedAddress, network.tokenBridgeAddress)).toNumber());
|
||||||
|
console.log((await TKN.balanceOf(network.tokenBridgeAddress)).toNumber());
|
||||||
|
} else {
|
||||||
|
throw new Error("Unkown command!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main();
|
|
@ -0,0 +1,113 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
npm run cleanup
|
||||||
|
if [! docker info > /dev/null ] ; then
|
||||||
|
echo "This script uses docker, and it isn't running - please start docker and try again!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if wormhole/ repo exists.
|
||||||
|
# If it doens't then clone and build guardiand
|
||||||
|
if [ ! -d "./wormhole" ]
|
||||||
|
then
|
||||||
|
git clone https://github.com/certusone/wormhole
|
||||||
|
cd wormhole/
|
||||||
|
DOCKER_BUILDKIT=1 docker build --target go-export -f Dockerfile.proto -o type=local,dest=node .
|
||||||
|
DOCKER_BUILDKIT=1 docker build --target node-export -f Dockerfile.proto -o type=local,dest=. .
|
||||||
|
cd node/
|
||||||
|
echo "Have patience, this step takes upwards of 500 seconds!"
|
||||||
|
if [ $(uname -m) = "arm64" ]; then
|
||||||
|
echo "Building Guardian for linux/amd64"
|
||||||
|
DOCKER_BUILDKIT=1 docker build --platform linux/amd64 -f Dockerfile -t guardian .
|
||||||
|
else
|
||||||
|
echo "Building Guardian natively"
|
||||||
|
DOCKER_BUILDKIT=1 docker build -f Dockerfile -t guardian .
|
||||||
|
fi
|
||||||
|
cd ../../
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Start EVM Chain 0
|
||||||
|
npx pm2 start 'ganache -p 8545 -m "myth like bonus scare over problem client lizard pioneer submit female collect" --block-time 1' --name evm0
|
||||||
|
# Start EVM Chain 1
|
||||||
|
npx pm2 start 'ganache -p 8546 -m "myth like bonus scare over problem client lizard pioneer submit female collect" --block-time 1' --name evm1
|
||||||
|
#Install Wormhole Eth Dependencies
|
||||||
|
cd wormhole/ethereum
|
||||||
|
npm i
|
||||||
|
cp .env.test .env
|
||||||
|
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Deploy Wormhole Contracts to EVM Chain 0
|
||||||
|
npm run migrate && npx truffle exec scripts/deploy_test_token.js && npx truffle exec scripts/register_solana_chain.js && npx truffle exec scripts/register_terra_chain.js && npx truffle exec scripts/register_bsc_chain.js && npx truffle exec scripts/register_algo_chain.js
|
||||||
|
# Deploy Wormhole Contracts to EVM Chain 1
|
||||||
|
perl -pi -e 's/CHAIN_ID=0x2/CHAIN_ID=0x4/g' .env && perl -pi -e 's/8545/8546/g' truffle-config.js
|
||||||
|
npm run migrate && npx truffle exec scripts/deploy_test_token.js && npx truffle exec scripts/register_solana_chain.js && npx truffle exec scripts/register_terra_chain.js && npx truffle exec scripts/register_eth_chain.js && npx truffle exec scripts/register_algo_chain.js && nc -lkp 2000 0.0.0.0
|
||||||
|
perl -pi -e 's/CHAIN_ID=0x4/CHAIN_ID=0x2/g' .env && perl -pi -e 's/8546/8545/g' truffle-config.js
|
||||||
|
cd ../../
|
||||||
|
|
||||||
|
# Run Guardiand
|
||||||
|
if [ $(uname -m) = "arm64" ]; then
|
||||||
|
docker run -d --name guardiand -p 7070:7070 -p 7071:7071 -p 7073:7073 --platform linux/amd64 --hostname guardian-0 --cap-add=IPC_LOCK --entrypoint /guardiand guardian node \
|
||||||
|
--unsafeDevMode --guardianKey /tmp/bridge.key --publicRPC "[::]:7070" --publicWeb "[::]:7071" --adminSocket /tmp/admin.sock --dataDir /tmp/data \
|
||||||
|
--ethRPC ws://host.docker.internal:8545 \
|
||||||
|
--ethContract "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550" \
|
||||||
|
--bscRPC ws://host.docker.internal:8546 \
|
||||||
|
--bscContract "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550" \
|
||||||
|
--polygonRPC ws://host.docker.internal:8545 \
|
||||||
|
--avalancheRPC ws://host.docker.internal:8545 \
|
||||||
|
--auroraRPC ws://host.docker.internal:8545 \
|
||||||
|
--fantomRPC ws://host.docker.internal:8545 \
|
||||||
|
--oasisRPC ws://host.docker.internal:8545 \
|
||||||
|
--karuraRPC ws://host.docker.internal:8545 \
|
||||||
|
--acalaRPC ws://host.docker.internal:8545 \
|
||||||
|
--klaytnRPC ws://host.docker.internal:8545 \
|
||||||
|
--celoRPC ws://host.docker.internal:8545 \
|
||||||
|
--moonbeamRPC ws://host.docker.internal:8545 \
|
||||||
|
--neonRPC ws://host.docker.internal:8545 \
|
||||||
|
--terraWS ws://host.docker.internal:8545 \
|
||||||
|
--terra2WS ws://host.docker.internal:8545 \
|
||||||
|
--terraLCD https://host.docker.internal:1317 \
|
||||||
|
--terra2LCD http://host.docker.internal:1317 \
|
||||||
|
--terraContract terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5 \
|
||||||
|
--terra2Contract terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5 \
|
||||||
|
--solanaContract Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o \
|
||||||
|
--solanaWS ws://host.docker.internal:8900 \
|
||||||
|
--solanaRPC http://host.docker.internal:8899 \
|
||||||
|
--algorandIndexerRPC ws://host.docker.internal:8545 \
|
||||||
|
--algorandIndexerToken "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
|
||||||
|
--algorandAlgodToken "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
|
||||||
|
--algorandAlgodRPC https://host.docker.internal:4001 \
|
||||||
|
--algorandAppID "4"
|
||||||
|
else
|
||||||
|
docker run -d --name guardiand --network host --hostname guardian-0 --cap-add=IPC_LOCK --entrypoint /guardiand guardian node \
|
||||||
|
--unsafeDevMode --guardianKey /tmp/bridge.key --publicRPC "[::]:7070" --publicWeb "[::]:7071" --adminSocket /tmp/admin.sock --dataDir /tmp/data \
|
||||||
|
--ethRPC ws://localhost:8545 \
|
||||||
|
--ethContract "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550" \
|
||||||
|
--bscRPC ws://localhost:8546 \
|
||||||
|
--bscContract "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550" \
|
||||||
|
--polygonRPC ws://localhost:8545 \
|
||||||
|
--avalancheRPC ws://localhost:8545 \
|
||||||
|
--auroraRPC ws://localhost:8545 \
|
||||||
|
--fantomRPC ws://localhost:8545 \
|
||||||
|
--oasisRPC ws://localhost:8545 \
|
||||||
|
--karuraRPC ws://localhost:8545 \
|
||||||
|
--acalaRPC ws://localhost:8545 \
|
||||||
|
--klaytnRPC ws://localhost:8545 \
|
||||||
|
--celoRPC ws://localhost:8545 \
|
||||||
|
--moonbeamRPC ws://localhost:8545 \
|
||||||
|
--neonRPC ws://localhost:8545 \
|
||||||
|
--terraWS ws://localhost:8545 \
|
||||||
|
--terra2WS ws://localhost:8545 \
|
||||||
|
--terraLCD https://terra-terrad:1317 \
|
||||||
|
--terra2LCD http://localhost:1317 \
|
||||||
|
--terraContract terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5 \
|
||||||
|
--terra2Contract terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5 \
|
||||||
|
--solanaContract Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o \
|
||||||
|
--solanaWS ws://localhost:8900 \
|
||||||
|
--solanaRPC http://localhost:8899 \
|
||||||
|
--algorandIndexerRPC ws://localhost:8545 \
|
||||||
|
--algorandIndexerToken "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
|
||||||
|
--algorandAlgodToken "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
|
||||||
|
--algorandAlgodRPC https://localhost:4001 \
|
||||||
|
--algorandAppID "4"
|
||||||
|
fi
|
||||||
|
echo "Guardiand Running! To look at logs: \"docker logs guardiand -f\""
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"networks": {
|
||||||
|
"evm0": {
|
||||||
|
"type": "evm",
|
||||||
|
"wormholeChainId": 2,
|
||||||
|
"rpc": "http://localhost:8545",
|
||||||
|
"privateKey": "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d",
|
||||||
|
"bridgeAddress": "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550",
|
||||||
|
"tokenBridgeAddress": "0x0290FB167208Af455bB137780163b7B7a9a10C16",
|
||||||
|
"testToken": "0x2D8BE6BF0baA74e0A907016679CaE9190e80dD0A"
|
||||||
|
},
|
||||||
|
"evm1": {
|
||||||
|
"type": "evm",
|
||||||
|
"wormholeChainId": 4,
|
||||||
|
"rpc": "http://localhost:8546",
|
||||||
|
"privateKey": "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d",
|
||||||
|
"tokenBridgeAddress": "0x0290FB167208Af455bB137780163b7B7a9a10C16",
|
||||||
|
"bridgeAddress": "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550",
|
||||||
|
"testToken": "0x2D8BE6BF0baA74e0A907016679CaE9190e80dD0A"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"wormhole": {
|
||||||
|
"restAddress": "http://localhost:7071"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue