diff --git a/.gitleaksignore b/.gitleaksignore index 7091a3a..e34077c 100644 --- a/.gitleaksignore +++ b/.gitleaksignore @@ -52,3 +52,4 @@ ac5929932dad792efef12b2860753ca90bc4b3de:scripts/proposals/proposal_04_CM_guard_ c91f775ec4afeb1737af25b8325201c090d48c23:scripts/deployment/globals_mainnet.json:generic-api-key:1 52a1209b5f91ae48f0c44a0ca968f69a6461d6a8:scripts/proposals/proposal_04_CM_guard_goerli.js:generic-api-key:14 52a1209b5f91ae48f0c44a0ca968f69a6461d6a8:scripts/proposals/proposal_04_CM_guard_goerli.js:generic-api-key:18 +fa17ac186753911eb9d2ae0a4ab59db5f7e8e563:scripts/deployment/bridges/solana/test/sepolia/timelock_set_upgrade_authority.js:generic-api-key:40 diff --git a/abis/test/WormholeCore.json b/abis/test/WormholeCore.json new file mode 100644 index 0000000..56df1fd --- /dev/null +++ b/abis/test/WormholeCore.json @@ -0,0 +1 @@ +[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousAdmin","type":"address"},{"indexed":false,"internalType":"address","name":"newAdmin","type":"address"}],"name":"AdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"beacon","type":"address"}],"name":"BeaconUpgraded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldContract","type":"address"},{"indexed":true,"internalType":"address","name":"newContract","type":"address"}],"name":"ContractUpgraded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint32","name":"index","type":"uint32"}],"name":"GuardianSetAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint64","name":"sequence","type":"uint64"},{"indexed":false,"internalType":"uint32","name":"nonce","type":"uint32"},{"indexed":false,"internalType":"bytes","name":"payload","type":"bytes"},{"indexed":false,"internalType":"uint8","name":"consistencyLevel","type":"uint8"}],"name":"LogMessagePublished","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"chainId","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"evmChainId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentGuardianSetIndex","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"index","type":"uint32"}],"name":"getGuardianSet","outputs":[{"components":[{"internalType":"address[]","name":"keys","type":"address[]"},{"internalType":"uint32","name":"expirationTime","type":"uint32"}],"internalType":"struct Structs.GuardianSet","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getGuardianSetExpiry","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"governanceActionIsConsumed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"governanceChainId","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"governanceContract","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isFork","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"impl","type":"address"}],"name":"isInitialized","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"messageFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"emitter","type":"address"}],"name":"nextSequence","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"encodedVM","type":"bytes"}],"name":"parseAndVerifyVM","outputs":[{"components":[{"internalType":"uint8","name":"version","type":"uint8"},{"internalType":"uint32","name":"timestamp","type":"uint32"},{"internalType":"uint32","name":"nonce","type":"uint32"},{"internalType":"uint16","name":"emitterChainId","type":"uint16"},{"internalType":"bytes32","name":"emitterAddress","type":"bytes32"},{"internalType":"uint64","name":"sequence","type":"uint64"},{"internalType":"uint8","name":"consistencyLevel","type":"uint8"},{"internalType":"bytes","name":"payload","type":"bytes"},{"internalType":"uint32","name":"guardianSetIndex","type":"uint32"},{"components":[{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"uint8","name":"guardianIndex","type":"uint8"}],"internalType":"struct Structs.Signature[]","name":"signatures","type":"tuple[]"},{"internalType":"bytes32","name":"hash","type":"bytes32"}],"internalType":"struct Structs.VM","name":"vm","type":"tuple"},{"internalType":"bool","name":"valid","type":"bool"},{"internalType":"string","name":"reason","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"encodedUpgrade","type":"bytes"}],"name":"parseContractUpgrade","outputs":[{"components":[{"internalType":"bytes32","name":"module","type":"bytes32"},{"internalType":"uint8","name":"action","type":"uint8"},{"internalType":"uint16","name":"chain","type":"uint16"},{"internalType":"address","name":"newContract","type":"address"}],"internalType":"struct GovernanceStructs.ContractUpgrade","name":"cu","type":"tuple"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"encodedUpgrade","type":"bytes"}],"name":"parseGuardianSetUpgrade","outputs":[{"components":[{"internalType":"bytes32","name":"module","type":"bytes32"},{"internalType":"uint8","name":"action","type":"uint8"},{"internalType":"uint16","name":"chain","type":"uint16"},{"components":[{"internalType":"address[]","name":"keys","type":"address[]"},{"internalType":"uint32","name":"expirationTime","type":"uint32"}],"internalType":"struct Structs.GuardianSet","name":"newGuardianSet","type":"tuple"},{"internalType":"uint32","name":"newGuardianSetIndex","type":"uint32"}],"internalType":"struct GovernanceStructs.GuardianSetUpgrade","name":"gsu","type":"tuple"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"encodedRecoverChainId","type":"bytes"}],"name":"parseRecoverChainId","outputs":[{"components":[{"internalType":"bytes32","name":"module","type":"bytes32"},{"internalType":"uint8","name":"action","type":"uint8"},{"internalType":"uint256","name":"evmChainId","type":"uint256"},{"internalType":"uint16","name":"newChainId","type":"uint16"}],"internalType":"struct GovernanceStructs.RecoverChainId","name":"rci","type":"tuple"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"encodedSetMessageFee","type":"bytes"}],"name":"parseSetMessageFee","outputs":[{"components":[{"internalType":"bytes32","name":"module","type":"bytes32"},{"internalType":"uint8","name":"action","type":"uint8"},{"internalType":"uint16","name":"chain","type":"uint16"},{"internalType":"uint256","name":"messageFee","type":"uint256"}],"internalType":"struct GovernanceStructs.SetMessageFee","name":"smf","type":"tuple"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"encodedTransferFees","type":"bytes"}],"name":"parseTransferFees","outputs":[{"components":[{"internalType":"bytes32","name":"module","type":"bytes32"},{"internalType":"uint8","name":"action","type":"uint8"},{"internalType":"uint16","name":"chain","type":"uint16"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes32","name":"recipient","type":"bytes32"}],"internalType":"struct GovernanceStructs.TransferFees","name":"tf","type":"tuple"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"encodedVM","type":"bytes"}],"name":"parseVM","outputs":[{"components":[{"internalType":"uint8","name":"version","type":"uint8"},{"internalType":"uint32","name":"timestamp","type":"uint32"},{"internalType":"uint32","name":"nonce","type":"uint32"},{"internalType":"uint16","name":"emitterChainId","type":"uint16"},{"internalType":"bytes32","name":"emitterAddress","type":"bytes32"},{"internalType":"uint64","name":"sequence","type":"uint64"},{"internalType":"uint8","name":"consistencyLevel","type":"uint8"},{"internalType":"bytes","name":"payload","type":"bytes"},{"internalType":"uint32","name":"guardianSetIndex","type":"uint32"},{"components":[{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"uint8","name":"guardianIndex","type":"uint8"}],"internalType":"struct Structs.Signature[]","name":"signatures","type":"tuple[]"},{"internalType":"bytes32","name":"hash","type":"bytes32"}],"internalType":"struct Structs.VM","name":"vm","type":"tuple"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint32","name":"nonce","type":"uint32"},{"internalType":"bytes","name":"payload","type":"bytes"},{"internalType":"uint8","name":"consistencyLevel","type":"uint8"}],"name":"publishMessage","outputs":[{"internalType":"uint64","name":"sequence","type":"uint64"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"numGuardians","type":"uint256"}],"name":"quorum","outputs":[{"internalType":"uint256","name":"numSignaturesRequiredForQuorum","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"_vm","type":"bytes"}],"name":"submitContractUpgrade","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"_vm","type":"bytes"}],"name":"submitNewGuardianSet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"_vm","type":"bytes"}],"name":"submitRecoverChainId","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"_vm","type":"bytes"}],"name":"submitSetMessageFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"_vm","type":"bytes"}],"name":"submitTransferFees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"},{"components":[{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"uint8","name":"guardianIndex","type":"uint8"}],"internalType":"struct Structs.Signature[]","name":"signatures","type":"tuple[]"},{"components":[{"internalType":"address[]","name":"keys","type":"address[]"},{"internalType":"uint32","name":"expirationTime","type":"uint32"}],"internalType":"struct Structs.GuardianSet","name":"guardianSet","type":"tuple"}],"name":"verifySignatures","outputs":[{"internalType":"bool","name":"valid","type":"bool"},{"internalType":"string","name":"reason","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"components":[{"internalType":"uint8","name":"version","type":"uint8"},{"internalType":"uint32","name":"timestamp","type":"uint32"},{"internalType":"uint32","name":"nonce","type":"uint32"},{"internalType":"uint16","name":"emitterChainId","type":"uint16"},{"internalType":"bytes32","name":"emitterAddress","type":"bytes32"},{"internalType":"uint64","name":"sequence","type":"uint64"},{"internalType":"uint8","name":"consistencyLevel","type":"uint8"},{"internalType":"bytes","name":"payload","type":"bytes"},{"internalType":"uint32","name":"guardianSetIndex","type":"uint32"},{"components":[{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"uint8","name":"guardianIndex","type":"uint8"}],"internalType":"struct Structs.Signature[]","name":"signatures","type":"tuple[]"},{"internalType":"bytes32","name":"hash","type":"bytes32"}],"internalType":"struct Structs.VM","name":"vm","type":"tuple"}],"name":"verifyVM","outputs":[{"internalType":"bool","name":"valid","type":"bool"},{"internalType":"string","name":"reason","type":"string"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/contracts/bridges/test/MockTimelock.sol b/contracts/bridges/test/MockTimelock.sol index 031174e..fea15fc 100644 --- a/contracts/bridges/test/MockTimelock.sol +++ b/contracts/bridges/test/MockTimelock.sol @@ -1,25 +1,25 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.25; -error ExecFailed(address fxRoot, bytes payload); +error ExecFailed(address relayer, bytes payload); /// @title MockTimelock - Mock of Timelock contract on the L1 side /// @author Aleksandr Kuperman - /// @author AL contract MockTimelock { // Fx Root address on L1 - address public immutable fxRoot; + address public immutable relayer; - constructor(address _fxRoot) { - fxRoot = _fxRoot; + constructor(address _relayer) { + relayer = _relayer; } /// @dev Executes the payload at the Fx Root address. /// @param payload Bytes of payload. function execute(bytes memory payload) external payable { - (bool success, ) = fxRoot.call{value: msg.value}(payload); + (bool success, ) = relayer.call{value: msg.value}(payload); if (!success) { - revert ExecFailed(fxRoot, payload); + revert ExecFailed(relayer, payload); } } } \ No newline at end of file diff --git a/docs/Vulnerabilities_list_governance.pdf b/docs/Vulnerabilities_list_governance.pdf index 6f2ed38..eb1e9a7 100644 Binary files a/docs/Vulnerabilities_list_governance.pdf and b/docs/Vulnerabilities_list_governance.pdf differ diff --git a/scripts/deployment/bridges/solana/test/polygon/deploy_00_mock_timelock.js b/scripts/deployment/bridges/solana/test/polygon/deploy_00_mock_timelock.js new file mode 100644 index 0000000..828d308 --- /dev/null +++ b/scripts/deployment/bridges/solana/test/polygon/deploy_00_mock_timelock.js @@ -0,0 +1,62 @@ +/*global process*/ + +const { ethers } = require("hardhat"); + +async function main() { + const fs = require("fs"); + const globalsFile = "globals.json"; + const dataFromJSON = fs.readFileSync(globalsFile, "utf8"); + let parsedData = JSON.parse(dataFromJSON); + const providerName = parsedData.providerName; + + let networkURL = parsedData.networkURL; + if (providerName === "polygon") { + if (!process.env.ALCHEMY_API_KEY_MATIC) { + console.log("set ALCHEMY_API_KEY_MATIC env variable"); + } + networkURL += process.env.ALCHEMY_API_KEY_MATIC; + } else if (providerName === "polygonAmoy") { + if (!process.env.ALCHEMY_API_KEY_AMOY) { + console.log("set ALCHEMY_API_KEY_AMOY env variable"); + return; + } + networkURL += process.env.ALCHEMY_API_KEY_AMOY; + } + + const provider = new ethers.providers.JsonRpcProvider(networkURL); + const signers = await ethers.getSigners(); + + // EOA address + const EOA = signers[0]; + const deployer = await EOA.getAddress(); + console.log("EOA is:", deployer); + + // Transaction signing and execution + console.log("1. EOA to deploy mock timelock contract"); + const Timelock = await ethers.getContractFactory("MockTimelock"); + console.log("You are signing the following transaction: Timelock.connect(EOA).deploy(WormholeAddress)"); + const timelock = await Timelock.connect(EOA).deploy(parsedData.wormholeAddress); + const result = await timelock.deployed(); + + // Transaction details + console.log("Contract deployment: MockTimelock"); + console.log("Contract address:", timelock.address); + console.log("Transaction:", result.deployTransaction.hash); + + // Writing updated parameters back to the JSON file + parsedData.timelockAddress = timelock.address; + fs.writeFileSync(globalsFile, JSON.stringify(parsedData)); + + // Contract verification + if (parsedData.contractVerification) { + const execSync = require("child_process").execSync; + execSync("npx hardhat verify --constructor-args scripts/deployment/bridges/solana/test/polygon/verify_00_mock_timelock.js --network " + providerName + " " + timelock.address, { encoding: "utf-8" }); + } +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/scripts/deployment/bridges/solana/test/polygon/globals.json b/scripts/deployment/bridges/solana/test/polygon/globals.json new file mode 100644 index 0000000..2fbfef3 --- /dev/null +++ b/scripts/deployment/bridges/solana/test/polygon/globals.json @@ -0,0 +1 @@ +{"contractVerification":true,"providerName":"polygon","networkURL":"https://polygon-mainnet.g.alchemy.com/v2/","gasPriceInGwei":"40","wormholeAddress":"0x7A4B5a56256163F07b2C80A7cA55aBE66c4ec4d7","timelockAddress":""} \ No newline at end of file diff --git a/scripts/deployment/bridges/solana/test/polygon/timelock_send_message.js b/scripts/deployment/bridges/solana/test/polygon/timelock_send_message.js new file mode 100644 index 0000000..3aca88c --- /dev/null +++ b/scripts/deployment/bridges/solana/test/polygon/timelock_send_message.js @@ -0,0 +1,54 @@ +/*global process*/ + +const { ethers } = require("ethers"); + +async function main() { + const ALCHEMY_API_KEY_MATIC = process.env.ALCHEMY_API_KEY_MATIC; + const polygonURL = "https://polygon-mainnet.g.alchemy.com/v2/" + ALCHEMY_API_KEY_MATIC; + const polygonProvider = new ethers.providers.JsonRpcProvider(polygonURL); + await polygonProvider.getBlockNumber().then((result) => { + console.log("Current block number polygon: " + result); + }); + + const fs = require("fs"); + // Wormhole Core address on polygon + const wormholeAddress = "0x7A4B5a56256163F07b2C80A7cA55aBE66c4ec4d7"; + const wormholeJSON = "abis/test/WormholeCore.json"; + let contractFromJSON = fs.readFileSync(wormholeJSON, "utf8"); + const wormholeABI = JSON.parse(contractFromJSON); + const wormhole = new ethers.Contract(wormholeAddress, wormholeABI, polygonProvider); + + // Mock Timelock contract address on goerli (has Wormhole Core address in it already) + const mockTimelockAddress = "0x4cEB52802ef86edF8796632546d89e55c87a0901"; + const mockTimelockJSON = "artifacts/contracts/bridges/test/MockTimelock.sol/MockTimelock.json"; + contractFromJSON = fs.readFileSync(mockTimelockJSON, "utf8"); + const parsedFile = JSON.parse(contractFromJSON); + const mockTimelockABI = parsedFile["abi"]; + const mockTimelock = new ethers.Contract(mockTimelockAddress, mockTimelockABI, polygonProvider); + + // Get the EOA + const account = ethers.utils.HDNode.fromMnemonic(process.env.TESTNET_MNEMONIC).derivePath("m/44'/60'/0'/0/0"); + const EOApolygon = new ethers.Wallet(account, polygonProvider); + console.log(EOApolygon.address); + + // Wrap the data to send over the bridge + // hello world == 68656c6c6f20776f726c64 + const data = "0x68656c6c6f20776f726c64"; + + const wormholeFinality = 0; // 0 = confirmed, 1 = finalized + const payload = wormhole.interface.encodeFunctionData("publishMessage", [0, data, wormholeFinality]); + console.log(payload); + const gasPrice = ethers.utils.parseUnits("40", "gwei"); + const tx = await mockTimelock.connect(EOApolygon).execute(payload, { gasPrice }); + console.log("Timelock data execution hash", tx.hash); + await tx.wait(); + + // https://wormholescan.io/#/tx/0x7b0145014a4e8f0d8621fbc0e366460dda3ba307732eff539f7c1e8e6589718a?view=advanced +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/scripts/deployment/bridges/solana/test/polygon/verify_00_mock_timelock.js b/scripts/deployment/bridges/solana/test/polygon/verify_00_mock_timelock.js new file mode 100644 index 0000000..4cbd41c --- /dev/null +++ b/scripts/deployment/bridges/solana/test/polygon/verify_00_mock_timelock.js @@ -0,0 +1,8 @@ +const fs = require("fs"); +const globalsFile = "globals.json"; +const dataFromJSON = fs.readFileSync(globalsFile, "utf8"); +const parsedData = JSON.parse(dataFromJSON); + +module.exports = [ + parsedData.wormholeAddress +]; \ No newline at end of file diff --git a/scripts/deployment/bridges/solana/test/sepolia/deploy_00_mock_timelock.js b/scripts/deployment/bridges/solana/test/sepolia/deploy_00_mock_timelock.js new file mode 100644 index 0000000..e62f9b4 --- /dev/null +++ b/scripts/deployment/bridges/solana/test/sepolia/deploy_00_mock_timelock.js @@ -0,0 +1,48 @@ +/*global process*/ + +const { ethers } = require("hardhat"); + +async function main() { + const fs = require("fs"); + const globalsFile = "globals.json"; + const dataFromJSON = fs.readFileSync(globalsFile, "utf8"); + let parsedData = JSON.parse(dataFromJSON); + const providerName = parsedData.providerName; + + const provider = await ethers.providers.getDefaultProvider(providerName); + const signers = await ethers.getSigners(); + + // EOA address + const EOA = signers[0]; + const deployer = await EOA.getAddress(); + console.log("EOA is:", deployer); + + // Transaction signing and execution + console.log("1. EOA to deploy mock timelock contract"); + const Timelock = await ethers.getContractFactory("MockTimelock"); + console.log("You are signing the following transaction: Timelock.connect(EOA).deploy(WormholeAddress)"); + const timelock = await Timelock.connect(EOA).deploy(parsedData.wormholeAddress); + const result = await timelock.deployed(); + + // Transaction details + console.log("Contract deployment: MockTimelock"); + console.log("Contract address:", timelock.address); + console.log("Transaction:", result.deployTransaction.hash); + + // Writing updated parameters back to the JSON file + parsedData.timelockAddress = timelock.address; + fs.writeFileSync(globalsFile, JSON.stringify(parsedData)); + + // Contract verification + if (parsedData.contractVerification) { + const execSync = require("child_process").execSync; + execSync("npx hardhat verify --constructor-args scripts/deployment/bridges/solana/test/sepolia/verify_00_mock_timelock.js --network " + providerName + " " + timelock.address, { encoding: "utf-8" }); + } +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/scripts/deployment/bridges/solana/test/sepolia/globals.json b/scripts/deployment/bridges/solana/test/sepolia/globals.json new file mode 100644 index 0000000..0095eee --- /dev/null +++ b/scripts/deployment/bridges/solana/test/sepolia/globals.json @@ -0,0 +1 @@ +{"contractVerification":true,"providerName":"sepolia","gasPriceInGwei":"30","wormholeAddress":"0x4a8bc80Ed5a4067f1CCf107057b8270E0cC11A78","timelockAddress":"0x471B3f60f08C50dd0eCba1bCd113B66FCC02b63d"} \ No newline at end of file diff --git a/scripts/deployment/bridges/solana/test/sepolia/timelock_send_message.js b/scripts/deployment/bridges/solana/test/sepolia/timelock_send_message.js new file mode 100644 index 0000000..c06e875 --- /dev/null +++ b/scripts/deployment/bridges/solana/test/sepolia/timelock_send_message.js @@ -0,0 +1,51 @@ +/*global process*/ + +const { ethers } = require("ethers"); + +async function main() { + const ALCHEMY_API_KEY_SEPOLIA = process.env.ALCHEMY_API_KEY_SEPOLIA; + const sepoliaURL = "https://eth-sepolia.g.alchemy.com/v2/" + ALCHEMY_API_KEY_SEPOLIA; + const sepoliaProvider = new ethers.providers.JsonRpcProvider(sepoliaURL); + await sepoliaProvider.getBlockNumber().then((result) => { + console.log("Current block number sepolia: " + result); + }); + + const fs = require("fs"); + // Wormhole Core address on sepolia + const wormholeAddress = "0x4a8bc80Ed5a4067f1CCf107057b8270E0cC11A78"; + const wormholeJSON = "abis/test/WormholeCore.json"; + let contractFromJSON = fs.readFileSync(wormholeJSON, "utf8"); + const wormholeABI = JSON.parse(contractFromJSON); + const wormhole = new ethers.Contract(wormholeAddress, wormholeABI, sepoliaProvider); + + // Mock Timelock contract address on goerli (has Wormhole Core address in it already) + const mockTimelockAddress = "0x471B3f60f08C50dd0eCba1bCd113B66FCC02b63d"; + const mockTimelockJSON = "artifacts/contracts/bridges/test/MockTimelock.sol/MockTimelock.json"; + contractFromJSON = fs.readFileSync(mockTimelockJSON, "utf8"); + const parsedFile = JSON.parse(contractFromJSON); + const mockTimelockABI = parsedFile["abi"]; + const mockTimelock = new ethers.Contract(mockTimelockAddress, mockTimelockABI, sepoliaProvider); + + // Get the EOA + const account = ethers.utils.HDNode.fromMnemonic(process.env.TESTNET_MNEMONIC).derivePath("m/44'/60'/0'/0/0"); + const EOAsepolia = new ethers.Wallet(account, sepoliaProvider); + console.log(EOAsepolia.address); + + // Wrap the data to send over the bridge + // hello world == 68656c6c6f20776f726c64 + const data = "0x68656c6c6f20776f726c64"; + + const wormholeFinality = 0; // 0 = confirmed, 1 = finalized + const payload = wormhole.interface.encodeFunctionData("publishMessage", [0, data, wormholeFinality]); + console.log(payload); + const tx = await mockTimelock.connect(EOAsepolia).execute(payload); + console.log("Timelock data execution hash", tx.hash); + await tx.wait(); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/scripts/deployment/bridges/solana/test/sepolia/timelock_set_upgrade_authority.js b/scripts/deployment/bridges/solana/test/sepolia/timelock_set_upgrade_authority.js new file mode 100644 index 0000000..e7dbd68 --- /dev/null +++ b/scripts/deployment/bridges/solana/test/sepolia/timelock_set_upgrade_authority.js @@ -0,0 +1,60 @@ +/*global process*/ + +const { ethers } = require("ethers"); + +async function main() { + const ALCHEMY_API_KEY_SEPOLIA = process.env.ALCHEMY_API_KEY_SEPOLIA; + const sepoliaURL = "https://eth-sepolia.g.alchemy.com/v2/" + ALCHEMY_API_KEY_SEPOLIA; + const sepoliaProvider = new ethers.providers.JsonRpcProvider(sepoliaURL); + await sepoliaProvider.getBlockNumber().then((result) => { + console.log("Current block number sepolia: " + result); + }); + + const fs = require("fs"); + // Wormhole Core address on sepolia + const wormholeAddress = "0x4a8bc80Ed5a4067f1CCf107057b8270E0cC11A78"; + const wormholeJSON = "abis/test/WormholeCore.json"; + let contractFromJSON = fs.readFileSync(wormholeJSON, "utf8"); + const wormholeABI = JSON.parse(contractFromJSON); + const wormhole = new ethers.Contract(wormholeAddress, wormholeABI, sepoliaProvider); + + // Mock Timelock contract address on goerli (has Wormhole Core address in it already) + const mockTimelockAddress = "0x471B3f60f08C50dd0eCba1bCd113B66FCC02b63d"; + const mockTimelockJSON = "artifacts/contracts/bridges/test/MockTimelock.sol/MockTimelock.json"; + contractFromJSON = fs.readFileSync(mockTimelockJSON, "utf8"); + const parsedFile = JSON.parse(contractFromJSON); + const mockTimelockABI = parsedFile["abi"]; + const mockTimelock = new ethers.Contract(mockTimelockAddress, mockTimelockABI, sepoliaProvider); + + // Get the EOA + const account = ethers.utils.HDNode.fromMnemonic(process.env.TESTNET_MNEMONIC).derivePath("m/44'/60'/0'/0/0"); + const EOAsepolia = new ethers.Wallet(account, sepoliaProvider); + console.log(EOAsepolia.address); + + // Wrap the data to send over the bridge + // Upgrade authority selector: 3 => u8 or 1 byte + const upgradeAuthoritySelector = "03"; + // Program account address (1okwt4nGbpr82kkr6t1767sAenfeZBxUyzJAAaumZRG) in hex16: 32 bytes + const programAddress = "0034de7cd749206ba916b55439381fb6b519e11757ee5ec12422151589bd337f"; + // Upgrade authority address (9fit3w7t6FHATDaZWotpWqN7NpqgL3Lm1hqUop4hAy8h) in hex16: 32 bytes + const authorityAddress = "80c8eb941bc025de8750e8a753630e2b6e7b96d5132f5f3f5a9228caf6521c26"; + + // Assemble the data + const data = "0x" + upgradeAuthoritySelector + programAddress + authorityAddress; + + const wormholeFinality = 0; // 0 = confirmed, 1 = finalized + const payload = wormhole.interface.encodeFunctionData("publishMessage", [0, data, wormholeFinality]); + console.log(payload); + const tx = await mockTimelock.connect(EOAsepolia).execute(payload); + console.log("Timelock data execution hash", tx.hash); + await tx.wait(); + + // tx: 0x577390c8d516cb74bec3d87c42fb50c454f87864e2dbe39839a1753407ca1e96 +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/scripts/deployment/bridges/solana/test/sepolia/timelock_transfer_all.js b/scripts/deployment/bridges/solana/test/sepolia/timelock_transfer_all.js new file mode 100644 index 0000000..2df0add --- /dev/null +++ b/scripts/deployment/bridges/solana/test/sepolia/timelock_transfer_all.js @@ -0,0 +1,64 @@ +/*global process*/ + +const { ethers } = require("ethers"); + +async function main() { + const ALCHEMY_API_KEY_SEPOLIA = process.env.ALCHEMY_API_KEY_SEPOLIA; + const sepoliaURL = "https://eth-sepolia.g.alchemy.com/v2/" + ALCHEMY_API_KEY_SEPOLIA; + const sepoliaProvider = new ethers.providers.JsonRpcProvider(sepoliaURL); + await sepoliaProvider.getBlockNumber().then((result) => { + console.log("Current block number sepolia: " + result); + }); + + const fs = require("fs"); + // Wormhole Core address on sepolia + const wormholeAddress = "0x4a8bc80Ed5a4067f1CCf107057b8270E0cC11A78"; + const wormholeJSON = "abis/test/WormholeCore.json"; + let contractFromJSON = fs.readFileSync(wormholeJSON, "utf8"); + const wormholeABI = JSON.parse(contractFromJSON); + const wormhole = new ethers.Contract(wormholeAddress, wormholeABI, sepoliaProvider); + + // Mock Timelock contract address on goerli (has Wormhole Core address in it already) + const mockTimelockAddress = "0x471B3f60f08C50dd0eCba1bCd113B66FCC02b63d"; + const mockTimelockJSON = "artifacts/contracts/bridges/test/MockTimelock.sol/MockTimelock.json"; + contractFromJSON = fs.readFileSync(mockTimelockJSON, "utf8"); + const parsedFile = JSON.parse(contractFromJSON); + const mockTimelockABI = parsedFile["abi"]; + const mockTimelock = new ethers.Contract(mockTimelockAddress, mockTimelockABI, sepoliaProvider); + + // Get the EOA + const account = ethers.utils.HDNode.fromMnemonic(process.env.TESTNET_MNEMONIC).derivePath("m/44'/60'/0'/0/0"); + const EOAsepolia = new ethers.Wallet(account, sepoliaProvider); + console.log(EOAsepolia.address); + + // Wrap the data to send over the bridge + // Transfer selector: 1 => u8 or 1 byte + const transferSelector = "01"; + // Source address (ATA) SOL (DygsTMRqfNbRMTmczXyTMaKuQtkPNPQVsGe2zMhKP4rG) in hex16: 32 bytes + const sourceAddressSol = "c0d1d976aaf0bc1676d1d7391c4aba6feeb3db165eb9718503486aba26c100a5"; + // Source address (ATA) OLAS (HJzBPrhZyMk3rbrnWxpRXXfPiymNXrWkP77rfo6MBh9e) in hex16: 32 bytes + const sourceAddressOlas = "f256846e9e70dd9acd35d810de70d21ba4242c60c3d74ec146a5c949c0199a05"; + // Destination address (ATA) SOL (JACBSWcdwYRFeP8Ab5v2d4Z2AVH4v4Fx2JTomqKARd6m) in hex16: 32 bytes + const destinationAddressSol = "fef18bd9d863fe9221ccecc49230be8cd53c5c543654d715136137c99c9570ee"; + // Destination address (ATA) OLAS (7e8LRrfeeSGfS2SSVGJMZQLQKzYhkBp8VKtt34uJMR4t) in hex16: 32 bytes + const destinationAddressOlas = "62a8e7b9a914c06fd22809b7e2d1baab4fa41a5429b10bebbe51b4dc32310ab1"; + + // Assemble the data + const data = "0x" + transferSelector + sourceAddressSol + sourceAddressOlas + destinationAddressSol + destinationAddressOlas; + + const wormholeFinality = 0; // 0 = confirmed, 1 = finalized + const payload = wormhole.interface.encodeFunctionData("publishMessage", [0, data, wormholeFinality]); + console.log(payload); + const tx = await mockTimelock.connect(EOAsepolia).execute(payload); + console.log("Timelock data execution hash", tx.hash); + await tx.wait(); + + // tx: 0x4818d0a9a89173797ff716ee34cc9c5a88f36a62acdfe4e484ebe6c37835a139 +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/scripts/deployment/bridges/solana/test/sepolia/timelock_transfer_sol.js b/scripts/deployment/bridges/solana/test/sepolia/timelock_transfer_sol.js new file mode 100644 index 0000000..eb32ee9 --- /dev/null +++ b/scripts/deployment/bridges/solana/test/sepolia/timelock_transfer_sol.js @@ -0,0 +1,66 @@ +/*global process Buffer*/ + +const { ethers } = require("ethers"); + +async function main() { + const ALCHEMY_API_KEY_SEPOLIA = process.env.ALCHEMY_API_KEY_SEPOLIA; + const sepoliaURL = "https://eth-sepolia.g.alchemy.com/v2/" + ALCHEMY_API_KEY_SEPOLIA; + const sepoliaProvider = new ethers.providers.JsonRpcProvider(sepoliaURL); + await sepoliaProvider.getBlockNumber().then((result) => { + console.log("Current block number sepolia: " + result); + }); + + const fs = require("fs"); + // Wormhole Core address on sepolia + const wormholeAddress = "0x4a8bc80Ed5a4067f1CCf107057b8270E0cC11A78"; + const wormholeJSON = "abis/test/WormholeCore.json"; + let contractFromJSON = fs.readFileSync(wormholeJSON, "utf8"); + const wormholeABI = JSON.parse(contractFromJSON); + const wormhole = new ethers.Contract(wormholeAddress, wormholeABI, sepoliaProvider); + + // Mock Timelock contract address on goerli (has Wormhole Core address in it already) + const mockTimelockAddress = "0x471B3f60f08C50dd0eCba1bCd113B66FCC02b63d"; + const mockTimelockJSON = "artifacts/contracts/bridges/test/MockTimelock.sol/MockTimelock.json"; + contractFromJSON = fs.readFileSync(mockTimelockJSON, "utf8"); + const parsedFile = JSON.parse(contractFromJSON); + const mockTimelockABI = parsedFile["abi"]; + const mockTimelock = new ethers.Contract(mockTimelockAddress, mockTimelockABI, sepoliaProvider); + + // Get the EOA + const account = ethers.utils.HDNode.fromMnemonic(process.env.TESTNET_MNEMONIC).derivePath("m/44'/60'/0'/0/0"); + const EOAsepolia = new ethers.Wallet(account, sepoliaProvider); + console.log(EOAsepolia.address); + + // Wrap the data to send over the bridge + // Transfer selector: 0 => u8 or 1 byte + const transferSelector = "00"; + // SOL address (So11111111111111111111111111111111111111112) in hex16: 32 bytes + //const solAddress = "069b8857feab8184fb687f634618c035dac439dc1aeb3b5598a0f00000000001"; + // Source address (DygsTMRqfNbRMTmczXyTMaKuQtkPNPQVsGe2zMhKP4rG) in hex16: 32 bytes + const sourceAddress = "c0d1d976aaf0bc1676d1d7391c4aba6feeb3db165eb9718503486aba26c100a5"; + // Destination address (JACBSWcdwYRFeP8Ab5v2d4Z2AVH4v4Fx2JTomqKARd6m) in hex16: 32 bytes + const destinationAddress = "fef18bd9d863fe9221ccecc49230be8cd53c5c543654d715136137c99c9570ee"; + // Amount to transfer: 100000 => u64 or 8 bytes + const amount = 100000; // "00000000000186a0" + const amountBuf = Buffer.allocUnsafe(8); + amountBuf.writeBigUInt64BE(BigInt(amount), 0); + + // Assemble the data + const data = "0x" + transferSelector + sourceAddress + destinationAddress + amountBuf.toString("hex"); + + const wormholeFinality = 0; // 0 = confirmed, 1 = finalized + const payload = wormhole.interface.encodeFunctionData("publishMessage", [0, data, wormholeFinality]); + console.log(payload); + const tx = await mockTimelock.connect(EOAsepolia).execute(payload); + console.log("Timelock data execution hash", tx.hash); + await tx.wait(); + + // tx: 0x1adf02b0844a9cd85f69635330703aff37a8b396434408d71efc184fb1915702 +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/scripts/deployment/bridges/solana/test/sepolia/timelock_transfer_token_accounts.js b/scripts/deployment/bridges/solana/test/sepolia/timelock_transfer_token_accounts.js new file mode 100644 index 0000000..abe04d6 --- /dev/null +++ b/scripts/deployment/bridges/solana/test/sepolia/timelock_transfer_token_accounts.js @@ -0,0 +1,62 @@ +/*global process*/ + +const { ethers } = require("ethers"); + +async function main() { + const ALCHEMY_API_KEY_SEPOLIA = process.env.ALCHEMY_API_KEY_SEPOLIA; + const sepoliaURL = "https://eth-sepolia.g.alchemy.com/v2/" + ALCHEMY_API_KEY_SEPOLIA; + const sepoliaProvider = new ethers.providers.JsonRpcProvider(sepoliaURL); + await sepoliaProvider.getBlockNumber().then((result) => { + console.log("Current block number sepolia: " + result); + }); + + const fs = require("fs"); + // Wormhole Core address on sepolia + const wormholeAddress = "0x4a8bc80Ed5a4067f1CCf107057b8270E0cC11A78"; + const wormholeJSON = "abis/test/WormholeCore.json"; + let contractFromJSON = fs.readFileSync(wormholeJSON, "utf8"); + const wormholeABI = JSON.parse(contractFromJSON); + const wormhole = new ethers.Contract(wormholeAddress, wormholeABI, sepoliaProvider); + + // Mock Timelock contract address on goerli (has Wormhole Core address in it already) + const mockTimelockAddress = "0x471B3f60f08C50dd0eCba1bCd113B66FCC02b63d"; + const mockTimelockJSON = "artifacts/contracts/bridges/test/MockTimelock.sol/MockTimelock.json"; + contractFromJSON = fs.readFileSync(mockTimelockJSON, "utf8"); + const parsedFile = JSON.parse(contractFromJSON); + const mockTimelockABI = parsedFile["abi"]; + const mockTimelock = new ethers.Contract(mockTimelockAddress, mockTimelockABI, sepoliaProvider); + + // Get the EOA + const account = ethers.utils.HDNode.fromMnemonic(process.env.TESTNET_MNEMONIC).derivePath("m/44'/60'/0'/0/0"); + const EOAsepolia = new ethers.Wallet(account, sepoliaProvider); + console.log(EOAsepolia.address); + + // Wrap the data to send over the bridge + // Transfer selector: 2 => u8 or 1 byte + const transferSelector = "02"; + // Source address (ATA) SOL (DygsTMRqfNbRMTmczXyTMaKuQtkPNPQVsGe2zMhKP4rG) in hex16: 32 bytes + const sourceAddressSol = "c0d1d976aaf0bc1676d1d7391c4aba6feeb3db165eb9718503486aba26c100a5"; + // Source address (ATA) OLAS (HJzBPrhZyMk3rbrnWxpRXXfPiymNXrWkP77rfo6MBh9e) in hex16: 32 bytes + const sourceAddressOlas = "f256846e9e70dd9acd35d810de70d21ba4242c60c3d74ec146a5c949c0199a05"; + // Destination address (9fit3w7t6FHATDaZWotpWqN7NpqgL3Lm1hqUop4hAy8h) in hex16: 32 bytes + const destinationAddress = "80c8eb941bc025de8750e8a753630e2b6e7b96d5132f5f3f5a9228caf6521c26"; + + // Assemble the data + const data = "0x" + transferSelector + sourceAddressSol + sourceAddressOlas + destinationAddress; + + const wormholeFinality = 0; // 0 = confirmed, 1 = finalized + const payload = wormhole.interface.encodeFunctionData("publishMessage", [0, data, wormholeFinality]); + console.log(payload); + const tx = await mockTimelock.connect(EOAsepolia).execute(payload); + console.log("Timelock data execution hash", tx.hash); + await tx.wait(); + + // tx: 0x70a103c57b85c65cb1364ee37b7f502f57d41fcb567e607da18033537db2c7db +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/scripts/deployment/bridges/solana/test/sepolia/timelock_upgrade_program.js b/scripts/deployment/bridges/solana/test/sepolia/timelock_upgrade_program.js new file mode 100644 index 0000000..4838a38 --- /dev/null +++ b/scripts/deployment/bridges/solana/test/sepolia/timelock_upgrade_program.js @@ -0,0 +1,62 @@ +/*global process*/ + +const { ethers } = require("ethers"); + +async function main() { + const ALCHEMY_API_KEY_SEPOLIA = process.env.ALCHEMY_API_KEY_SEPOLIA; + const sepoliaURL = "https://eth-sepolia.g.alchemy.com/v2/" + ALCHEMY_API_KEY_SEPOLIA; + const sepoliaProvider = new ethers.providers.JsonRpcProvider(sepoliaURL); + await sepoliaProvider.getBlockNumber().then((result) => { + console.log("Current block number sepolia: " + result); + }); + + const fs = require("fs"); + // Wormhole Core address on sepolia + const wormholeAddress = "0x4a8bc80Ed5a4067f1CCf107057b8270E0cC11A78"; + const wormholeJSON = "abis/test/WormholeCore.json"; + let contractFromJSON = fs.readFileSync(wormholeJSON, "utf8"); + const wormholeABI = JSON.parse(contractFromJSON); + const wormhole = new ethers.Contract(wormholeAddress, wormholeABI, sepoliaProvider); + + // Mock Timelock contract address on goerli (has Wormhole Core address in it already) + const mockTimelockAddress = "0x471B3f60f08C50dd0eCba1bCd113B66FCC02b63d"; + const mockTimelockJSON = "artifacts/contracts/bridges/test/MockTimelock.sol/MockTimelock.json"; + contractFromJSON = fs.readFileSync(mockTimelockJSON, "utf8"); + const parsedFile = JSON.parse(contractFromJSON); + const mockTimelockABI = parsedFile["abi"]; + const mockTimelock = new ethers.Contract(mockTimelockAddress, mockTimelockABI, sepoliaProvider); + + // Get the EOA + const account = ethers.utils.HDNode.fromMnemonic(process.env.TESTNET_MNEMONIC).derivePath("m/44'/60'/0'/0/0"); + const EOAsepolia = new ethers.Wallet(account, sepoliaProvider); + console.log(EOAsepolia.address); + + // Wrap the data to send over the bridge + // Upgrade authority selector: 4 => u8 or 1 byte + const upgradeAuthoritySelector = "04"; + // Program account address (1okwt4nGbpr82kkr6t1767sAenfeZBxUyzJAAaumZRG) in hex16: 32 bytes + const programAddress = "0034de7cd749206ba916b55439381fb6b519e11757ee5ec12422151589bd337f"; + // New program buffer account address (EpFxEcCuf1mndxvkYsoYhuXb8oYWvDsGwYCq5gLVqzv3) in hex16: 32 bytes + const programBufferAddress = "cd43227c7d908a196ade9d713e13b57d0605831f2aecde168d42ee4f8d08b0e8"; + // Spill account address (9fit3w7t6FHATDaZWotpWqN7NpqgL3Lm1hqUop4hAy8h) in hex16: 32 bytes + const spillAddress = "80c8eb941bc025de8750e8a753630e2b6e7b96d5132f5f3f5a9228caf6521c26"; + + // Assemble the data + const data = "0x" + upgradeAuthoritySelector + programAddress + programBufferAddress + spillAddress; + + const wormholeFinality = 0; // 0 = confirmed, 1 = finalized + const payload = wormhole.interface.encodeFunctionData("publishMessage", [0, data, wormholeFinality]); + console.log(payload); + const tx = await mockTimelock.connect(EOAsepolia).execute(payload); + console.log("Timelock data execution hash", tx.hash); + await tx.wait(); + + // tx: 0xf643f2b2f0e095cf20f6d395032afd2a2526551b9e03ae3b2cf0c5bde77d3c42 +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/scripts/deployment/bridges/solana/test/sepolia/verify_00_mock_timelock.js b/scripts/deployment/bridges/solana/test/sepolia/verify_00_mock_timelock.js new file mode 100644 index 0000000..4cbd41c --- /dev/null +++ b/scripts/deployment/bridges/solana/test/sepolia/verify_00_mock_timelock.js @@ -0,0 +1,8 @@ +const fs = require("fs"); +const globalsFile = "globals.json"; +const dataFromJSON = fs.readFileSync(globalsFile, "utf8"); +const parsedData = JSON.parse(dataFromJSON); + +module.exports = [ + parsedData.wormholeAddress +]; \ No newline at end of file