import { exec } from "child_process"; import fs from "fs"; import { BigNumber, 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 install && 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.`); const tokenAmt = ethers.utils.parseUnits(process.argv[4], "18"); console.log(`Minting ${tokenAmt} tokens.`); await TKN.mint(deployment[process.argv[2]].deployedAddress, tokenAmt, { gasLimit: 2000000 }); 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 propagate 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 ); //Multiply out the decimals to 18 (default for ERC20) const bridgeAmt = ethers.utils.parseUnits(process.argv[5], "18"); // Remember to allow Token Bridge to move tokens from Treasury account to it's own account console.log(`Approving ${process.argv[5]} (${bridgeAmt} in proper ERC20 format) Tokens to be bridged by Token Bridge`); await treasury.approveTokenBridge(bridgeAmt, { gasLimit: 2000000, }); await new Promise((r) => setTimeout(r, 5000)); //Time out to let block propagate 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 ${process.argv[5]} Tokens which is ${bridgeAmt} Tokens`); const targetRecepient = Buffer.from(tryNativeToHexString(targetDeployment.deployedAddress, "ethereum"), 'hex'); const tx = await (await treasury.bridgeToken(bridgeAmt, 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();