Merge branch 'mvp'

This commit is contained in:
spacemandev 2022-08-15 17:15:19 -06:00
commit 5a0b9b9075
46 changed files with 4383 additions and 3 deletions

9
.gitmodules vendored
View File

@ -4,3 +4,12 @@
[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
[submodule "projects/xmint/chains/evm/lib/openzeppelin-contracts"]
path = projects/xmint/chains/evm/lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
[submodule "projects/xmint/chains/evm/lib/solidity-bytes-utils"]
path = projects/xmint/chains/evm/lib/solidity-bytes-utils
url = https://github.com/GNSPS/solidity-bytes-utils
[submodule "projects/xmint/chains/evm/lib/forge-std"]
path = projects/xmint/chains/evm/lib/forge-std
url = https://github.com/foundry-rs/forge-std

View File

@ -16,9 +16,9 @@ cd wormhole/ethereum
make .env 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_eth_chain.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_bsc_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
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_bsc_chain.js
perl -pi -e 's/CHAIN_ID=0x4/CHAIN_ID=0x2/g' .env && perl -pi -e 's/8546/8545/g' truffle-config.js

View File

@ -2,7 +2,10 @@
set -euo pipefail
# Start guardiand
# Check if Docker is running
docker ps > /dev/null #route error to console
echo "Docker is running"
# Start guardiand
npx pm2 delete guardiand 2> /dev/null || true
npx pm2 start 'bash guardiand.bash' --name guardiand

3
projects/xmint/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
deployinfo/*.deploy.json
keypairs/*.key
node_modules/

2
projects/xmint/chains/evm/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
out/
cache/

View File

@ -0,0 +1,7 @@
[default]
solc_version = "0.8.13"
src = 'src'
out = 'out'
libs = ['lib']
# See more config options https://github.com/foundry-rs/foundry/tree/master/config

@ -0,0 +1 @@
Subproject commit 2c7cbfc6fbede6d7c9e6b17afe997e3fdfe22fef

@ -0,0 +1 @@
Subproject commit 2dc086563f2e51620ebc43d2237fc10ef201c4e6

@ -0,0 +1 @@
Subproject commit 6458fb2780a3092bc756e737f246be1de6d3d362

View File

@ -0,0 +1,2 @@
@openzeppelin=lib/openzeppelin-contracts
solidity-bytes-utils=lib/solidity-bytes-utils/contracts

View File

@ -0,0 +1,12 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Script.sol";
contract ContractScript is Script {
function setUp() public {}
function run() public {
vm.broadcast();
}
}

View File

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

View File

@ -0,0 +1,98 @@
// contracts/Structs.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
contract BridgeStructs {
struct Transfer {
// PayloadID uint8 = 1
uint8 payloadID;
// Amount being transferred (big-endian uint256)
uint256 amount;
// Address of the token. Left-zero-padded if shorter than 32 bytes
bytes32 tokenAddress;
// Chain ID of the token
uint16 tokenChain;
// Address of the recipient. Left-zero-padded if shorter than 32 bytes
bytes32 to;
// Chain ID of the recipient
uint16 toChain;
// Amount of tokens (big-endian uint256) that the user is willing to pay as relayer fee. Must be <= Amount.
uint256 fee;
}
struct TransferWithPayload {
// PayloadID uint8 = 3
uint8 payloadID;
// Amount being transferred (big-endian uint256)
uint256 amount;
// Address of the token. Left-zero-padded if shorter than 32 bytes
bytes32 tokenAddress;
// Chain ID of the token
uint16 tokenChain;
// Address of the recipient. Left-zero-padded if shorter than 32 bytes
bytes32 to;
// Chain ID of the recipient
uint16 toChain;
// Address of the message sender. Left-zero-padded if shorter than 32 bytes
bytes32 fromAddress;
// An arbitrary payload
bytes payload;
}
struct TransferResult {
// Chain ID of the token
uint16 tokenChain;
// Address of the token. Left-zero-padded if shorter than 32 bytes
bytes32 tokenAddress;
// Amount being transferred (big-endian uint256)
uint256 normalizedAmount;
// Amount of tokens (big-endian uint256) that the user is willing to pay as relayer fee. Must be <= Amount.
uint256 normalizedArbiterFee;
// Portion of msg.value to be paid as the core bridge fee
uint wormholeFee;
}
struct AssetMeta {
// PayloadID uint8 = 2
uint8 payloadID;
// Address of the token. Left-zero-padded if shorter than 32 bytes
bytes32 tokenAddress;
// Chain ID of the token
uint16 tokenChain;
// Number of decimals of the token (big-endian uint256)
uint8 decimals;
// Symbol of the token (UTF-8)
bytes32 symbol;
// Name of the token (UTF-8)
bytes32 name;
}
struct RegisterChain {
// Governance Header
// module: "TokenBridge" left-padded
bytes32 module;
// governance action: 1
uint8 action;
// governance paket chain id: this or 0
uint16 chainId;
// Chain ID
uint16 emitterChainID;
// Emitter address. Left-zero-padded if shorter than 32 bytes
bytes32 emitterAddress;
}
struct UpgradeContract {
// Governance Header
// module: "TokenBridge" left-padded
bytes32 module;
// governance action: 2
uint8 action;
// governance paket chain id
uint16 chainId;
// Address of the new contract
bytes32 newContract;
}
}

View File

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

View File

@ -0,0 +1,42 @@
// contracts/Messages.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
import "./Structs.sol";
interface IWormhole is Structs {
event LogMessagePublished(address indexed sender, uint64 sequence, uint32 nonce, bytes payload, uint8 consistencyLevel);
function publishMessage(
uint32 nonce,
bytes memory payload,
uint8 consistencyLevel
) external payable returns (uint64 sequence);
function parseAndVerifyVM(bytes calldata encodedVM) external view returns (Structs.VM memory vm, bool valid, string memory reason);
function verifyVM(Structs.VM memory vm) external view returns (bool valid, string memory reason);
function verifySignatures(bytes32 hash, Structs.Signature[] memory signatures, Structs.GuardianSet memory guardianSet) external pure returns (bool valid, string memory reason) ;
function parseVM(bytes memory encodedVM) external pure returns (Structs.VM memory vm);
function getGuardianSet(uint32 index) external view returns (Structs.GuardianSet memory) ;
function getCurrentGuardianSetIndex() external view returns (uint32) ;
function getGuardianSetExpiry() external view returns (uint32) ;
function governanceActionIsConsumed(bytes32 hash) external view returns (bool) ;
function isInitialized(address impl) external view returns (bool) ;
function chainId() external view returns (uint16) ;
function governanceChainId() external view returns (uint16);
function governanceContract() external view returns (bytes32);
function messageFee() external view returns (uint256) ;
}

View File

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

View File

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

View File

@ -0,0 +1,75 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "./Wormhole/IWormhole.sol";
import "./Wormhole/ITokenBridge.sol";
import "./Wormhole/BridgeStructs.sol";
import "solidity-bytes-utils/BytesLib.sol";
contract Xmint is ERC20 {
using BytesLib for bytes;
mapping(uint16 => bytes32) _applicationContracts;
mapping(bytes32 => bool) _completedMessages;
address owner;
IWormhole core_bridge;
ITokenBridge token_bridge;
uint32 nonce = 0;
event Log(string indexed str);
constructor(
string memory name_,
string memory symbol_,
address coreBridgeAddress,
address tokenBridgeAddress
) ERC20(name_, symbol_) {
owner = msg.sender;
core_bridge = IWormhole(coreBridgeAddress);
token_bridge = ITokenBridge(tokenBridgeAddress);
}
/**
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;
}
/**
Takes inventory of the foreign currency
Mints tokens to self
Transfers tokens with Payload 1 to Receipient on Foreign chain
*/
function submitForeignPurchase(bytes memory encodedVm) public returns (uint64) {
// Complete transfer will give the Tokens to this Contract
BridgeStructs.TransferWithPayload memory vaa = _decodePayload(token_bridge.completeTransferWithPayload(encodedVm));
// Mint tokens to this contract
//amt they paid is NATIVE
//multiply by 100 to get how many tokens to give out
uint256 amtToMint = vaa.amount * 100;
_mint(address(this), amtToMint);
// Give Token Bridge allowance to take tokens from this contract
this.approve(address(token_bridge), amtToMint);
// Transfer tokens via Token Bridge over to Recipient in payload
uint64 sequence = token_bridge.transferTokens(address(this), amtToMint, vaa.tokenChain, bytes32(vaa.payload), 0, nonce);
nonce += 1;
return sequence;
}
function _decodePayload(bytes memory payload) internal pure returns (BridgeStructs.TransferWithPayload memory) {
BridgeStructs.TransferWithPayload memory decoded = BridgeStructs.TransferWithPayload({
payloadID: payload.slice(0,1).toUint8(0),
amount: payload.slice(1,32).toUint256(0),
tokenAddress: payload.slice(33,32).toBytes32(0),
tokenChain: payload.slice(65,2).toUint16(0),
to: payload.slice(67,32).toBytes32(0),
toChain: payload.slice(99,2).toUint16(0),
fromAddress: payload.slice(101,32).toBytes32(0),
payload: payload.slice(133, payload.length-133)
});
return decoded;
}
}

View File

@ -0,0 +1,12 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
contract ContractTest is Test {
function setUp() public {}
function testExample() public {
assertTrue(true);
}
}

View File

@ -0,0 +1,7 @@
.anchor
.DS_Store
target
**/*.rs.bk
node_modules
test-ledger

View File

@ -0,0 +1,8 @@
.anchor
.DS_Store
target
node_modules
dist
build
test-ledger

View File

@ -0,0 +1,15 @@
[features]
seeds = false
skip-lint = false
[programs.localnet]
solana = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
[registry]
url = "https://api.apr.dev"
[provider]
cluster = "localnet"
wallet = "/Users/spacemandev/.config/solana/id.json"
[scripts]
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"

View File

@ -0,0 +1,13 @@
[workspace]
members = [
"programs/*"
]
[profile.release]
overflow-checks = true
lto = "fat"
codegen-units = 1
[profile.release.build-override]
opt-level = 3
incremental = false
codegen-units = 1

View File

@ -0,0 +1,12 @@
// Migrations are an early feature. Currently, they're nothing more than this
// single deploy script that's invoked from the CLI, injecting a provider
// configured from the workspace's Anchor.toml.
const anchor = require("@project-serum/anchor");
module.exports = async function (provider) {
// Configure client to use the provider.
anchor.setProvider(provider);
// Add your deploy script here.
};

View File

@ -0,0 +1,19 @@
{
"scripts": {
"lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w",
"lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
},
"dependencies": {
"@project-serum/anchor": "^0.25.0"
},
"devDependencies": {
"chai": "^4.3.4",
"mocha": "^9.0.3",
"ts-mocha": "^10.0.0",
"@types/bn.js": "^5.1.0",
"@types/chai": "^4.3.0",
"@types/mocha": "^9.0.0",
"typescript": "^4.3.5",
"prettier": "^2.6.2"
}
}

View File

@ -0,0 +1,19 @@
[package]
name = "solana"
version = "0.1.0"
description = "Created with Anchor"
edition = "2021"
[lib]
crate-type = ["cdylib", "lib"]
name = "solana"
[features]
no-entrypoint = []
no-idl = []
no-log-ix-name = []
cpi = ["no-entrypoint"]
default = []
[dependencies]
anchor-lang = "0.25.0"

View File

@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -0,0 +1,15 @@
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
pub mod solana {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize {}

View File

@ -0,0 +1,16 @@
import * as anchor from "@project-serum/anchor";
import { Program } from "@project-serum/anchor";
import { Solana } from "../target/types/solana";
describe("solana", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Solana as Program<Solana>;
it("Is initialized!", async () => {
// Add your test here.
const tx = await program.methods.initialize().rpc();
console.log("Your transaction signature", tx);
});
});

View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"types": ["mocha", "chai"],
"typeRoots": ["./node_modules/@types"],
"lib": ["es2015"],
"module": "commonjs",
"target": "es6",
"esModuleInterop": true
}
}

File diff suppressed because it is too large Load Diff

View File

View File

@ -0,0 +1,451 @@
import * as fs from 'fs';
import {
attestFromEth,
createWrappedOnEth,
getEmitterAddressEth,
getEmitterAddressSolana,
getForeignAssetEth,
parseSequenceFromLogEth,
redeemOnEth,
setDefaultWasm,
transferFromEthNative,
tryNativeToUint8Array,
} from '@certusone/wormhole-sdk';
import * as ethers from 'ethers';
import fetch from 'node-fetch';
import { promisify } from 'util';
import * as solana from './solana';
const exec = promisify(require('child_process').exec);
const config = JSON.parse(fs.readFileSync('./xdapp.config.json').toString());
let ABI;
try {
ABI = JSON.parse(fs.readFileSync("./chains/evm/out/Xmint.sol/Xmint.json").toString()).abi
} catch (e) {
// fail silenty
// The only time this fails is when deploy hasn't been called, in which case, this isn't needed
}
/**
* 1. Deploy on chain contract "XMint"
* @param src The network to deploy
*/
export async function deploy(src: string){
const rpc = config.networks[src]['rpc'];
const core = config.networks[src]['bridgeAddress'];
const token = config.networks[src]['tokenBridgeAddress'];
const key = fs.readFileSync(`keypairs/${src}.key`).toString();
const { stdout , stderr } = await exec(
`cd chains/evm && forge build && forge create --legacy --rpc-url ${rpc} --private-key ${key} src/Xmint.sol:Xmint --constructor-args "${src.toUpperCase()}-TOKEN" "${src.toUpperCase()}T" ${core} ${token} && exit`
)
if (stderr) {
throw new Error(stderr.message);
}
let deploymentAddress:string;
if (stdout) {
console.log(stdout);
deploymentAddress = stdout
.split("Deployed to: ")[1]
.split("\n")[0]
.trim();
const emittedVAAs = []; //Resets the emittedVAAs
fs.writeFileSync(
`./deployinfo/${src}.deploy.json`,
JSON.stringify({
address: deploymentAddress,
vaas: emittedVAAs
}, null, 4)
);
}
}
/**
* Registers the cross chain mint contracts with one another across chains
* @param src The network you want to register the foreign network on.
* @param target The foreign network
*/
export async function registerApp(src:string, target:string){
const key = fs.readFileSync(`keypairs/${src}.key`).toString();
const srcNetwork = config.networks[src];
const targetNetwork = config.networks[target];
let srcDeploymentInfo;
let targetDeploymentInfo;
let targetEmitter;
try{
srcDeploymentInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
} catch (e){
throw new Error(`${src} is not deployed yet`);
}
try{
targetDeploymentInfo = JSON.parse(fs.readFileSync(`./deployinfo/${target}.deploy.json`).toString());
} catch (e){
throw new Error(`${target} is not deployed yet`);
}
switch (targetNetwork['type']){
case 'evm':
targetEmitter = getEmitterAddressEth(targetDeploymentInfo['address']);
break;
case 'solana':
setDefaultWasm("node"); // *sigh*
targetEmitter = await getEmitterAddressSolana(targetDeploymentInfo['address']);
break;
}
const emitterBuffer = Buffer.from(targetEmitter, 'hex');
const signer = new ethers.Wallet(key).connect(
new ethers.providers.JsonRpcProvider(srcNetwork.rpc)
);
const messenger = new ethers.Contract(
srcDeploymentInfo.address,
ABI,
signer
);
const tx = await messenger.registerApplicationContracts(
targetNetwork.wormholeChainId,
emitterBuffer
);
console.log(`Registered ${target} application on ${src}`);
// Alongside registering the App, go ahead register the tokens with one another
// Register target token with src chain
console.log(`Registering ${target} token on ${src}`);
switch(targetNetwork.type){
case 'evm':
await attest(target, src);
break;
case 'solana':
await solana.attest(target, src);
break;
}
console.log(`Attested ${target} token on ${src}`);
}
/**
* Attest token from src and create wrapped on target
* @param src
* @param target
* @param address
*/
export async function attest(src: string, target: string, address:string = null){
//Check TARGET type == EVM, else throw error
const srcNetwork = config.networks[src];
const targetNetwork = config.networks[target];
const srcDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
const targetDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${target}.deploy.json`).toString());
const srcKey = fs.readFileSync(`keypairs/${src}.key`).toString();
const srcSigner = new ethers.Wallet(srcKey).connect(
new ethers.providers.JsonRpcProvider(srcNetwork.rpc)
);
console.log(`Attesting ${src} Network Token on ${target} Network`)
if(!address){
address = srcDeployInfo.address;
}
console.log(`Attesting ${address} from ${target} network onto ${src}`);
const tx = await attestFromEth(
srcNetwork.tokenBridgeAddress,
srcSigner,
address,
{
gasLimit: 1500000
}
);
// in this context the target is network we're attesting *from* so it's the network the vaa comes from (hence being placed as the 'source')
// The emitter for this is PORTAL, not our contract, so we set portal=true in fetchVaa
const attestVaa = await fetchVaa(src, tx, true);
switch(targetNetwork.type){
case "evm":
await createWrapped(target, src, attestVaa)
break;
case "solana":
await solana.createWrapped(target, src, attestVaa)
break;
}
}
export async function createWrapped(src:string, target: string, vaa:string){
const srcNetwork = config.networks[src];
const targetNetwork = config.networks[target];
const targetDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${target}.deploy.json`).toString());
const key = fs.readFileSync(`keypairs/${src}.key`).toString();
const signer = new ethers.Wallet(key).connect(
new ethers.providers.JsonRpcProvider(srcNetwork.rpc)
);
const tx = await createWrappedOnEth(
srcNetwork.tokenBridgeAddress,
signer,
Buffer.from(vaa, 'base64'),
{
gasLimit: 1000000
}
);
await new Promise((r) => setTimeout(r, 5000)); // wait for blocks to advance before fetching new foreign address
const foreignAddress = await getForeignAssetEth(
srcNetwork.tokenBridgeAddress,
signer,
targetNetwork.wormholeChainId,
tryNativeToUint8Array(targetDeployInfo.address, targetNetwork.wormholeChainId)
);
console.log(`${src} Network has new PortalWrappedToken for ${target} network at ${foreignAddress}`);
}
async function fetchVaa(src:string, tx:ethers.ethers.ContractReceipt, portal:boolean = false){
const srcNetwork = config.networks[src];
const srcDeploymentInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
const seq = parseSequenceFromLogEth(tx, srcNetwork['bridgeAddress']);
let emitterAddr = "";
if(portal){
emitterAddr = getEmitterAddressEth(srcNetwork['tokenBridgeAddress']);
} else {
emitterAddr = getEmitterAddressEth(srcDeploymentInfo['address']);
}
await new Promise((r) => setTimeout(r, 5000)); //wait for Guardian to pick up message
console.log(
"Searching for: ",
`${config.wormhole.restAddress}/v1/signed_vaa/${srcNetwork.wormholeChainId}/${emitterAddr}/${seq}`
);
const vaaBytes = await (
await fetch(
`${config.wormhole.restAddress}/v1/signed_vaa/${srcNetwork.wormholeChainId}/${emitterAddr}/${seq}`
)
).json();
if(!vaaBytes['vaaBytes']){
throw new Error("VAA not found!");
}
console.log("VAA Found: ", vaaBytes.vaaBytes);
return vaaBytes.vaaBytes;
}
function writeVaa(src:string, vaa:string){
const srcDeploymentInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
if(!srcDeploymentInfo['vaas']){
srcDeploymentInfo['vaas'] = [vaa]
} else {
srcDeploymentInfo['vaas'].push(vaa)
}
fs.writeFileSync(
`./deployinfo/${src}.deploy.json`,
JSON.stringify(srcDeploymentInfo, null, 4)
);
}
/**
* Submits target Purchase VAA onto src network
* @param src The EVM type of network that the VAA is being submitted to
* @param target The target network which initiated the purchase
* @param vaa The b64 encoded VAA
*/
export async function submitForeignPurchase(src:string, target:string, vaa:string) : Promise<string> {
const srcNetwork = config.networks[src];
const key = fs.readFileSync(`keypairs/${src}.key`).toString();
let srcDeploymentInfo;
let targetDeploymentInfo;
try{
srcDeploymentInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
} catch (e){
throw new Error(`${src} is not deployed yet`);
}
try{
targetDeploymentInfo = JSON.parse(fs.readFileSync(`./deployinfo/${target}.deploy.json`).toString());
} catch (e){
throw new Error(`${target} is not deployed yet`);
}
const signer = new ethers.Wallet(key).connect(
new ethers.providers.JsonRpcProvider(srcNetwork.rpc)
);
const messenger = new ethers.Contract(
srcDeploymentInfo.address,
ABI,
signer
);
//This will mint tokens and create a VAA to transfer them back over to the src chain
const tx = await messenger.submitForeignPurchase(Buffer.from(vaa, "base64"), {gasLimit: 1000000});
console.log("Submit foreign purchase succeeded");
//Even though we call our contract, portal=true here because our contract calls Portal's transfer() function, making the emitter Portal
const claimTokensVaa = await fetchVaa(src, await tx.wait(), true);
return claimTokensVaa;
}
/**
* Claims the tokens generated on a foreign network onto the key in the source network
* @param src The chain you want to claim the vaa on
* @param vaa The vaa you want to claim
*/
export async function claimTokens(src:string, vaa:string){
const srcNetwork = config.networks[src];
const key = fs.readFileSync(`keypairs/${src}.key`).toString();
let srcDeploymentInfo;
try{
srcDeploymentInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
} catch (e){
throw new Error(`${src} is not deployed yet`);
}
const signer = new ethers.Wallet(key).connect(
new ethers.providers.JsonRpcProvider(srcNetwork.rpc)
);
console.log(`Redeeming on ${src} network`);
await redeemOnEth(
srcNetwork.tokenBridgeAddress,
signer,
Buffer.from(vaa, "base64"),
{
gasLimit: 1000000
}
)
}
export async function submitForeignSale(src:string, target:string, vaa:string){
}
/**
* Creates a P3 VAA that can only be redeemed by target contract with src key as recipient address
* @param src
* @param target
* @param amount
* @returns
*/
export async function buyToken(src:string, target: string, amount: number): Promise<string> {
//Buy Token on Target Chain with SRC Native Currency
// Create P3 VAA that pays X native and has the Receipient Address set to XMINT on Target Chain & payload is src key
const srcNetwork = config.networks[src];
const targetNetwork = config.networks[target];
const key = fs.readFileSync(`keypairs/${src}.key`).toString();
let srcDeploymentInfo;
let targetDeploymentInfo;
try{
srcDeploymentInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
} catch (e){
throw new Error(`${src} is not deployed yet`);
}
try{
targetDeploymentInfo = JSON.parse(fs.readFileSync(`./deployinfo/${target}.deploy.json`).toString());
} catch (e){
throw new Error(`${target} is not deployed yet`);
}
const signer = new ethers.Wallet(key).connect(
new ethers.providers.JsonRpcProvider(srcNetwork.rpc)
);
//For this project, 1 Native Token will always equal 100 Chain Tokens, no matter the source or target chains
const ethToTransferAmt = ethers.utils.parseUnits((amount/100).toString(), "18"); //how much native you want to transfer to buy AMT worth of Tokens on target chain
const targetChainAddress = tryNativeToUint8Array(targetDeploymentInfo.address, targetNetwork.wormholeChainId);
//The payload is just the purchaser's public key
// This is used to send a Payload 1 Transfer of Tokens back
// If it errors, will send a Refund VAA back
const purchaseOrderPayload = tryNativeToUint8Array(await signer.getAddress(), srcNetwork.wormholeChainId);
//Gotta approve the WETH transfer before we actually transfer using Token Bridge
const WETH = new ethers.Contract(
srcNetwork.wrappedNativeAddress,
JSON.parse(fs.readFileSync("./chains/evm/out/PortalWrappedToken.sol/PortalWrappedToken.json").toString()).abi,
signer
);
await WETH.approve(srcNetwork.tokenBridgeAddress, ethToTransferAmt);
console.log("WETH Approved");
// Now call token bridge to do the transfer
const tx = await transferFromEthNative(
srcNetwork.tokenBridgeAddress,
signer,
ethToTransferAmt,
targetNetwork.wormholeChainId,
targetChainAddress,
ethers.BigNumber.from(0),
{
gasPrice: 2000000000
},
purchaseOrderPayload
);
console.log("ETH Native Transferred");
// The buy order will be written to the SRC chain's vaa list
// Needs to be submitted to target chain with `submitForeignPurchase`
const vaa = await fetchVaa(src, tx, true);
writeVaa(src, vaa);
return vaa;
}
/**
* Fetches the balance of the SRC KEY on the SRC NETWORK for either SRC NATIVE CURRENCY or Target Tokens
* @param src
* @param target
* @returns
*/
export async function balance(src:string, target: string) : Promise<string> {
const srcNetwork = config.networks[src];
const targetNetwork = config.networks[target];
const key = fs.readFileSync(`keypairs/${src}.key`).toString();
let srcDeploymentInfo;
let targetDeploymentInfo;
try{
srcDeploymentInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
} catch (e){
throw new Error(`${src} is not deployed yet`);
}
try{
targetDeploymentInfo = JSON.parse(fs.readFileSync(`./deployinfo/${target}.deploy.json`).toString());
} catch (e){
throw new Error(`${target} is not deployed yet`);
}
const signer = new ethers.Wallet(key).connect(
new ethers.providers.JsonRpcProvider(srcNetwork.rpc)
);
if(src == target){
//Get native currency balance
return (await signer.getBalance()).toString()
}
// Else get the Token Balance of the Foreign Network's token on Src Network
const foreignAddress = await getForeignAssetEth(
srcNetwork.tokenBridgeAddress,
signer,
targetNetwork.wormholeChainId,
tryNativeToUint8Array(targetDeploymentInfo.address, targetNetwork.wormholeChainId)
);
const TKN = new ethers.Contract(
foreignAddress,
JSON.parse(fs.readFileSync("./chains/evm/out/PortalWrappedToken.sol/PortalWrappedToken.json").toString()).abi,
signer
);
return (<ethers.BigNumber>(await TKN.balanceOf(await signer.getAddress()))).div(1e8).toString()
}

View File

@ -0,0 +1,10 @@
export async function deploy(src: string){}
export async function attest(src: string, target:string, address:string = null){}
export async function registerApp(src:string, target:string){}
export async function createWrapped(src:string, target:string, vaa:string){}
export async function submitForeignPurchase(src:string, target:string, vaa:string){}
export async function submitForeignSale(src:string, target:string, vaa:string){}
export async function buyToken(src:string, target: string, amount:number){}
export async function sellToken(src:string, target:string, amount:number){}

View File

View File

View File

@ -0,0 +1 @@
0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d

View File

@ -0,0 +1 @@
0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d

View File

@ -0,0 +1 @@
J2D4pwDred8P9ioyPEZVLPht885AeYpifsFGUyuzVmiKQosAvmZP4EegaKFrSprBC5vVP1xTvu61vYDWsxBNsYx

View File

@ -0,0 +1,166 @@
import { Command } from 'commander';
import fs from 'fs';
import * as evm from './handlers/evm';
import * as solana from './handlers/solana';
const xmint = new Command();
const config = JSON.parse(fs.readFileSync('./xdapp.config.json').toString())
xmint
.name('xMint')
.description("Cross chain token minting, transfers, and widrawals.")
.version("0.1.0");
xmint
.command("deploy")
.description("Deploys on chain code.")
.argument("<src>", "the network you want to deply")
.action(async (src) => {
if(!config.networks[src]){
console.error(`ERROR: ${src} not found in xdapp.config.json`);
return;
}
console.log(`Deploying ${src}...`);
switch(config.networks[src].type){
case "evm":
await evm.deploy(src);
break;
case "solana":
await solana.deploy(src);
break;
}
console.log(`Deploy finished!`);
});
xmint
.command("register-app")
.description("Registers the target app and target token with the source on chain app")
.argument("<src>", "the network you want to register the app on")
.argument("<target>", "the network you want to register")
.action(async (src, target) => {
if(!config.networks[src]){
console.error(`ERROR: ${src} not found in xdapp.config.json`);
return;
}
if(!config.networks[target]){
console.error(`ERROR: ${target} not found in xdapp.config.json`);
return;
}
let srcHandler;
switch(config.networks[src].type){
case "evm":
srcHandler = evm;
break;
case "solana":
srcHandler = solana;
break;
}
console.log(`Registering ${target} app and token onto ${src} network`)
await srcHandler.registerApp(src,target)
try{
console.log(`Attesting ${src} Wrapped Native to ${target}`);
await srcHandler.attest(target, src, config.networks[src].wrappedNativeAddress)
} catch (e) {
console.log("Wrapped Native attestion exists already")
}
console.log(`${target} contract address was registered on ${src} and ${target} token was attested to ${src}`)
});
xmint
.command("buy-token")
.description("buy token using the src network's native currency and bridge it back to src network and hold as bridged funds")
.argument("<src>", "the network with the native token you want to spend")
.argument("<target>", "the network whose xmint token you want to buy")
.argument("<amt>", "amount of token to buy. Always 1 Native : 100 TOKEN")
.action(async (src, target, amt) => {
if(!config.networks[src]){
console.error(`ERROR: ${src} not found in xdapp.config.json`);
return;
}
if(!config.networks[target]){
console.error(`ERROR: ${target} not found in xdapp.config.json`);
return;
}
if(isNaN(parseInt(amt))){
console.error(`Error: Invalid Amount!`)
return;
}
// Buy Token On Source
// SubmitForeignPurchase on Target
// Claim Tokens on Source
let srcHandler;
switch(config.networks[src].type){
case "evm":
srcHandler = evm;
break;
case "solana":
srcHandler = solana;
break;
}
let targetHandler;
switch(config.networks[target].type){
case "evm":
targetHandler = evm;
break;
case "solana":
targetHandler = solana;
break;
}
console.log(`Creating Buy VAA on ${src} network...`);
const buyVAA = await srcHandler.buyToken(src, target, parseInt(amt));
console.log(`Submitting buy vaa on ${target} network...`);
const claimTokensVAA = await targetHandler.submitForeignPurchase(target, src, buyVAA);
console.log(`Claiming tokens on ${src} network...`);
await srcHandler.claimTokens(src, claimTokensVAA);
console.log(`Purchase of ${amt} of ${target} network's tokens complete.`)
});
xmint
.command("sell-token")
.description("sells token from target network back to target network for src network's native token")
.argument("<src>", "the network whose native currency you want to receive")
.argument("<target>", "the target network whose token you want to sell")
.argument("<amt>", "amount of token to sell. Always 1 TOKEN : 0.01 NATIVE")
.action(async (src, targe, amt) => {
// Sell Token on Source
// SubmitForeignSale on Target
// Claim Native
});
xmint
.command("balance")
.description("gets the balance of the target networks tokens for src network's keys. Pass in src src if you want find native tokens balance on src network")
.argument("<src>", "the network whose key you want to lookup the balance for")
.argument("<target>", "the network whose tokens you want to look up the balance for. Pass in src network again to get native token balance. ")
.action(async (src, target) => {
if(!config.networks[src]){
console.error(`ERROR: ${src} not found in xdapp.config.json`);
return;
}
if(!config.networks[target]){
console.error(`ERROR: ${target} not found in xdapp.config.json`);
return;
}
let balance;
switch(config.networks[src].type){
case "evm":
balance = await evm.balance(src, target);
break;
case "solana":
break;
}
console.log(`Balance of ${src} key for ${target} tokens is ${balance}`);
})
xmint.parse();

View File

@ -0,0 +1,16 @@
{
"name": "xmint",
"version": "1.0.0",
"description": "Multichain P3 Example",
"main": "orchestrator.ts",
"license": "MIT",
"dependencies": {
"@certusone/wormhole-sdk": "^0.6.1",
"@project-serum/anchor": "^0.25.0",
"@types/node": "^18.7.1",
"@types/node-fetch": "^2.6.2",
"commander": "^9.4.0",
"ethers": "^5.6.9",
"node-fetch": "2"
}
}

View File

@ -0,0 +1,20 @@
# Deploy the code
ts-node orchestrator.ts deploy evm0
ts-node orchestrator.ts deploy evm1
# Register Apps
ts-node orchestrator.ts register-app evm0 evm1
ts-node orchestrator.ts register-app evm1 evm0
# Print Balances
ts-node orchestrator.ts balance evm0 evm0
ts-node orchestrator.ts balance evm0 evm1
ts-node orchestrator.ts balance evm1 evm1
ts-node orchestrator.ts balance evm1 evm0
# Buy Tokens
ts-node orchestrator.ts buy-token evm0 evm1 100
# Print Balance
ts-node orchestrator.ts balance evm0 evm0
ts-node orchestrator.ts balance evm0 evm1

View File

@ -0,0 +1,37 @@
# Deploy the code on EVM0 and SOL0
ts-node orchestrator.ts deploy evm0
ts-node orchestrator.ts deploy sol0
# Print Balances for EVM0 and SOL0 Keypairs
ts-node orchestrator.ts balance evm0 evm0
ts-node orchestrator.ts balance evm0 sol0
ts-node orchestrator.ts balance sol0 sol0
ts-node orchestrator.ts balance sol0 evm0
# Register Apps EVM<>SOL
ts-node orchestrator.ts register-app evm0 sol0
ts-node orchestrator.ts register-app sol0 evm0
# Buy SOL0-TOKEN with eth
ts-node orchestrator.ts buy-token evm0 sol0 100
# Print SOL0 Balance
ts-node orchestrator.ts balance evm0 evm0
ts-node orchestrator.ts balance evm0 sol0
# Buy EVM0-TOKEN with sol
ts-node orchestrator.ts buy-token sol0 evm0 100
# Print SOL0 Balance for Solana and EVM0-TOKENS
ts-node orchestrator.ts balance sol0 sol0
ts-node orchesatrator.ts balance sol0 evm0
# Sell SOL0-TOKEN for eth
ts-node orchestrator.ts sell-token sol0 evm0 100
# Print SOL0 Balance for solana and EVM0-TOKENS
ts-node orchestrator.ts balance sol0 sol0
ts-node orchestrator.ts balance sol0 evm0
# Sell EVM0-TOKEN for solana
ts-node orchestrator.ts sell-token evm0 sol0 100
# Print EVM0 Balance for eth and SOL0-TOKENS
ts-node orchestrator.ts balance evm0 evm0
ts-node orchestrator.ts balance evm0 sol0

View File

@ -0,0 +1,7 @@
{
"compilerOptions": {
"types": ["node"],
"moduleResolution": "node",
"esModuleInterop": true
}
}

View File

@ -0,0 +1,31 @@
{
"networks": {
"evm0": {
"type": "evm",
"wormholeChainId": 2,
"rpc": "http://localhost:8545",
"bridgeAddress": "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550",
"tokenBridgeAddress": "0x0290FB167208Af455bB137780163b7B7a9a10C16",
"wrappedNativeAddress": "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E"
},
"evm1": {
"type": "evm",
"wormholeChainId": 4,
"rpc": "http://localhost:8546",
"bridgeAddress": "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550",
"tokenBridgeAddress": "0x0290FB167208Af455bB137780163b7B7a9a10C16",
"wrappedNativeAddress": "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E"
},
"sol0": {
"type": "solana",
"wormholeChainId": 1,
"rpc": "http://localhost:8899",
"bridgeAddress": "Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o",
"tokenBridgeAddress": "B6RHG3mfcckmrYN1UhmJzyS1XX3fZKbkeUcpJe9Sy3FE",
"wrappedNativeAddress": "So11111111111111111111111111111111111111112"
}
},
"wormhole": {
"restAddress": "http://localhost:7071"
}
}

1719
projects/xmint/yarn.lock Normal file

File diff suppressed because it is too large Load Diff