diff --git a/ethereum/contracts/mock/MockWormhole.sol b/ethereum/contracts/mock/MockWormhole.sol new file mode 100644 index 0000000..9805d07 --- /dev/null +++ b/ethereum/contracts/mock/MockWormhole.sol @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: Apache 2 + +pragma solidity ^0.8.17; + +import "../interfaces/IWormhole.sol"; +import "../libraries/external/BytesLib.sol"; + +contract MockWormhole is IWormhole { + using BytesLib for bytes; + + uint256 private constant VM_VERSION_SIZE = 1; + uint256 private constant VM_GUARDIAN_SET_SIZE = 4; + uint256 private constant VM_SIGNATURE_COUNT_SIZE = 1; + uint256 private constant VM_TIMESTAMP_SIZE = 4; + uint256 private constant VM_NONCE_SIZE = 4; + uint256 private constant VM_EMITTER_CHAIN_ID_SIZE = 2; + uint256 private constant VM_EMITTER_ADDRESS_SIZE = 32; + uint256 private constant VM_SEQUENCE_SIZE = 8; + uint256 private constant VM_CONSISTENCY_LEVEL_SIZE = 1; + uint256 private constant VM_SIZE_MINIMUM = VM_VERSION_SIZE + VM_GUARDIAN_SET_SIZE + VM_SIGNATURE_COUNT_SIZE + + VM_TIMESTAMP_SIZE + VM_NONCE_SIZE + VM_EMITTER_CHAIN_ID_SIZE + VM_EMITTER_ADDRESS_SIZE + VM_SEQUENCE_SIZE + + VM_CONSISTENCY_LEVEL_SIZE; + + uint256 private constant SIGNATURE_GUARDIAN_INDEX_SIZE = 1; + uint256 private constant SIGNATURE_R_SIZE = 32; + uint256 private constant SIGNATURE_S_SIZE = 32; + uint256 private constant SIGNATURE_V_SIZE = 1; + uint256 private constant SIGNATURE_SIZE_TOTAL = + SIGNATURE_GUARDIAN_INDEX_SIZE + SIGNATURE_R_SIZE + SIGNATURE_S_SIZE + SIGNATURE_V_SIZE; + + mapping(address => uint64) public sequences; + // Dictionary of VMs that must be mocked as invalid. + mapping(bytes32 => bool) public invalidVMs; + + uint256 currentMsgFee; + uint16 immutable wormholeChainId; + uint256 immutable boundEvmChainId; + + constructor(uint16 initChainId, uint256 initEvmChainId) { + wormholeChainId = initChainId; + boundEvmChainId = initEvmChainId; + } + + function invalidateVM(bytes calldata encodedVm) external { + VM memory vm = _parseVM(encodedVm); + invalidVMs[vm.hash] = true; + } + + function publishMessage(uint32 nonce, bytes memory payload, uint8 consistencyLevel) + external + payable + returns (uint64 sequence) + { + require(msg.value == currentMsgFee, "invalid fee"); + sequence = sequences[msg.sender]++; + emit LogMessagePublished(msg.sender, sequence, nonce, payload, consistencyLevel); + } + + function parseVM(bytes calldata encodedVm) external pure returns (VM memory vm) { + vm = _parseVM(encodedVm); + } + + function parseAndVerifyVM(bytes calldata encodedVm) + external + view + returns (VM memory vm, bool valid, string memory reason) + { + vm = _parseVM(encodedVm); + //behold the rigorous checking! + valid = !invalidVMs[vm.hash]; + reason = ""; + } + + function _parseVM(bytes calldata encodedVm) internal pure returns (VM memory vm) { + require(encodedVm.length >= VM_SIZE_MINIMUM, "vm too small"); + + bytes memory body; + + uint256 offset = 0; + vm.version = encodedVm.toUint8(offset); + offset += 1; + + vm.guardianSetIndex = encodedVm.toUint32(offset); + offset += 4; + + (vm.signatures, offset) = parseSignatures(encodedVm, offset); + + body = encodedVm[offset:]; + vm.timestamp = encodedVm.toUint32(offset); + offset += 4; + + vm.nonce = encodedVm.toUint32(offset); + offset += 4; + + vm.emitterChainId = encodedVm.toUint16(offset); + offset += 2; + + vm.emitterAddress = encodedVm.toBytes32(offset); + offset += 32; + + vm.sequence = encodedVm.toUint64(offset); + offset += 8; + + vm.consistencyLevel = encodedVm.toUint8(offset); + offset += 1; + + vm.payload = encodedVm[offset:]; + vm.hash = keccak256(abi.encodePacked(keccak256(body))); + } + + function parseSignatures(bytes calldata encodedVm, uint256 offset) + internal + pure + returns (Signature[] memory signatures, uint256 offsetAfterParse) + { + uint256 sigCount = uint256(encodedVm.toUint8(offset)); + offset += 1; + + require(encodedVm.length >= (VM_SIZE_MINIMUM + sigCount * SIGNATURE_SIZE_TOTAL), "vm too small"); + + signatures = new Signature[](sigCount); + for (uint256 i = 0; i < sigCount; ++i) { + uint8 guardianIndex = encodedVm.toUint8(offset); + offset += 1; + + bytes32 r = encodedVm.toBytes32(offset); + offset += 32; + + bytes32 s = encodedVm.toBytes32(offset); + offset += 32; + + uint8 v = encodedVm.toUint8(offset); + offset += 1; + + signatures[i] = Signature({ + r: r, + s: s, + // The hardcoded 27 comes from the base offset for public key recovery ids, public key type and network + // used in ECDSA signatures for bitcoin and ethereum. + // See https://bitcoin.stackexchange.com/a/5089 + v: v + 27, + guardianIndex: guardianIndex + }); + } + + return (signatures, offset); + } + + function initialize() external {} + + function quorum(uint256 /*numGuardians*/ ) external pure returns (uint256 /*numSignaturesRequiredForQuorum*/ ) { + return 1; + } + + /** + * General state and chain observers + */ + function chainId() external view returns (uint16) { + return wormholeChainId; + } + + function evmChainId() external view returns (uint256) { + return boundEvmChainId; + } + + function getCurrentGuardianSetIndex() external pure returns (uint32) { + return 0; + } + + function getGuardianSet(uint32 /*index*/ ) external pure returns (GuardianSet memory) { + revert("unsupported getGuardianSet in wormhole mock"); + } + + function getGuardianSetExpiry() external pure returns (uint32) { + return 0; + } + + function governanceActionIsConsumed(bytes32 /*hash*/ ) external pure returns (bool) { + return false; + } + + function isInitialized(address /*impl*/ ) external pure returns (bool) { + return true; + } + + function isFork() external pure returns (bool) { + return false; + } + + function governanceChainId() external pure returns (uint16) { + return 1; + } + + function governanceContract() external pure returns (bytes32) { + return bytes32(0x0000000000000000000000000000000000000000000000000000000000000004); + } + + function messageFee() external view returns (uint256) { + return currentMsgFee; + } + + function nextSequence(address emitter) external view returns (uint64) { + return sequences[emitter]; + } + + function verifyVM(VM memory /*vm*/ ) external pure returns (bool, /*valid*/ string memory /*reason*/ ) { + revert("unsupported verifyVM in wormhole mock"); + } + + function verifySignatures(bytes32, /*hash*/ Signature[] memory, /*signatures*/ GuardianSet memory /*guardianSet*/ ) + external + pure + returns (bool, /*valid*/ string memory /*reason*/ ) + { + revert("unsupported verifySignatures in wormhole mock"); + } + + function parseContractUpgrade(bytes memory /*encodedUpgrade*/ ) + external + pure + returns (ContractUpgrade memory /*cu*/ ) + { + revert("unsupported parseContractUpgrade in wormhole mock"); + } + + function parseGuardianSetUpgrade(bytes memory /*encodedUpgrade*/ ) + external + pure + returns (GuardianSetUpgrade memory /*gsu*/ ) + { + revert("unsupported parseGuardianSetUpgrade in wormhole mock"); + } + + function parseSetMessageFee(bytes memory /*encodedSetMessageFee*/ ) + external + pure + returns (SetMessageFee memory /*smf*/ ) + { + revert("unsupported parseSetMessageFee in wormhole mock"); + } + + function parseTransferFees(bytes memory /*encodedTransferFees*/ ) + external + pure + returns (TransferFees memory /*tf*/ ) + { + revert("unsupported parseTransferFees in wormhole mock"); + } + + function parseRecoverChainId(bytes memory /*encodedRecoverChainId*/ ) + external + pure + returns (RecoverChainId memory /*rci*/ ) + { + revert("unsupported parseRecoverChainId in wormhole mock"); + } + + function submitContractUpgrade(bytes memory /*_vm*/ ) external pure { + revert("unsupported submitContractUpgrade in wormhole mock"); + } + + function submitSetMessageFee(bytes memory /*_vm*/ ) external pure { + revert("unsupported submitSetMessageFee in wormhole mock"); + } + + function setMessageFee(uint256 newFee) external { + currentMsgFee = newFee; + } + + function submitNewGuardianSet(bytes memory /*_vm*/ ) external pure { + revert("unsupported submitNewGuardianSet in wormhole mock"); + } + + function submitTransferFees(bytes memory /*_vm*/ ) external pure { + revert("unsupported submitTransferFees in wormhole mock"); + } + + function submitRecoverChainId(bytes memory /*_vm*/ ) external pure { + revert("unsupported submitRecoverChainId in wormhole mock"); + } +} diff --git a/ethereum/forge-test/CoreRelayer.t.sol b/ethereum/forge-test/CoreRelayer.t.sol index 344bf2b..a16643f 100644 --- a/ethereum/forge-test/CoreRelayer.t.sol +++ b/ethereum/forge-test/CoreRelayer.t.sol @@ -17,11 +17,9 @@ import {CoreRelayerImplementation} from "../contracts/coreRelayer/CoreRelayerImp import {CoreRelayerProxy} from "../contracts/coreRelayer/CoreRelayerProxy.sol"; import {CoreRelayerMessages} from "../contracts/coreRelayer/CoreRelayerMessages.sol"; import {CoreRelayerStructs} from "../contracts/coreRelayer/CoreRelayerStructs.sol"; -import {Setup as WormholeSetup} from "../wormhole/ethereum/contracts/Setup.sol"; -import {Implementation as WormholeImplementation} from "../wormhole/ethereum/contracts/Implementation.sol"; -import {Wormhole} from "../wormhole/ethereum/contracts/Wormhole.sol"; +import {MockWormhole} from "../contracts/mock/MockWormhole.sol"; import {IWormhole} from "../contracts/interfaces/IWormhole.sol"; -import {WormholeSimulator} from "./WormholeSimulator.sol"; +import {WormholeSimulator, FakeWormholeSimulator} from "./WormholeSimulator.sol"; import {IWormholeReceiver} from "../contracts/interfaces/IWormholeReceiver.sol"; import {AttackForwardIntegration} from "../contracts/mock/AttackForwardIntegration.sol"; import {MockRelayerIntegration} from "../contracts/mock/MockRelayerIntegration.sol"; @@ -55,36 +53,15 @@ contract TestCoreRelayer is Test { WormholeSimulator relayerWormholeSimulator; function setUp() public { - WormholeSetup setup = new WormholeSetup(); - - // deploy Implementation - WormholeImplementation implementation = new WormholeImplementation(); - - // set guardian set - address[] memory guardians = new address[](1); - guardians[0] = 0xbeFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe; - // deploy Wormhole - relayerWormhole = IWormhole( - address( - new Wormhole( - address(setup), - abi.encodeWithSelector( - bytes4(keccak256("setup(address,address[],uint16,uint16,bytes32,uint256)")), - address(implementation), - guardians, - 2, // wormhole chain id - uint16(1), // governance chain id - 0x0000000000000000000000000000000000000000000000000000000000000004, // governance contract - block.chainid - ) - ) - ) - ); + MockWormhole wormhole = new MockWormhole({ + initChainId: 2, + initEvmChainId: block.chainid + }); - relayerWormholeSimulator = new WormholeSimulator( - address(relayerWormhole), - uint256(0xcfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0) + relayerWormhole = wormhole; + relayerWormholeSimulator = new FakeWormholeSimulator( + wormhole ); setUpChains(5); @@ -94,37 +71,18 @@ contract TestCoreRelayer is Test { internal returns (IWormhole wormholeContract, WormholeSimulator wormholeSimulator) { - // deploy Setup - WormholeSetup setup = new WormholeSetup(); - - // deploy Implementation - WormholeImplementation implementation = new WormholeImplementation(); - - // set guardian set - address[] memory guardians = new address[](1); - guardians[0] = 0xbeFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe; - // deploy Wormhole - Wormhole wormhole = new Wormhole( - address(setup), - abi.encodeWithSelector( - bytes4(keccak256("setup(address,address[],uint16,uint16,bytes32,uint256)")), - address(implementation), - guardians, - chainId, // wormhole chain id - uint16(1), // governance chain id - 0x0000000000000000000000000000000000000000000000000000000000000004, // governance contract - block.chainid - ) - ); + MockWormhole wormhole = new MockWormhole({ + initChainId: 2, + initEvmChainId: block.chainid + }); // replace Wormhole with the Wormhole Simulator contract (giving access to some nice helper methods for signing) - wormholeSimulator = new WormholeSimulator( - address(wormhole), - uint256(0xcfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0) + wormholeSimulator = new FakeWormholeSimulator( + wormhole ); - wormholeContract = IWormhole(wormholeSimulator.wormhole()); + wormholeContract = wormhole; } function setUpRelayProvider(uint16 chainId) internal returns (RelayProvider relayProvider) { @@ -144,7 +102,7 @@ contract TestCoreRelayer is Test { relayProvider = RelayProvider(address(myRelayProvider)); } - function setUpCoreRelayer(uint16 chainId, address wormhole, address defaultRelayProvider) + function setUpCoreRelayer(uint16 chainId, IWormhole wormhole, address defaultRelayProvider) internal returns (IWormholeRelayer coreRelayer) { @@ -159,8 +117,8 @@ contract TestCoreRelayer is Test { chainId, wormhole, defaultRelayProvider, - uint16(1), // governance chain id - 0x0000000000000000000000000000000000000000000000000000000000000004, // governance contract + wormhole.governanceChainId(), + wormhole.governanceContract(), block.chainid ) ) @@ -274,7 +232,7 @@ contract TestCoreRelayer is Test { Contracts memory mapEntry; (mapEntry.wormhole, mapEntry.wormholeSimulator) = setUpWormhole(i); mapEntry.relayProvider = setUpRelayProvider(i); - mapEntry.coreRelayer = setUpCoreRelayer(i, address(mapEntry.wormhole), address(mapEntry.relayProvider)); + mapEntry.coreRelayer = setUpCoreRelayer(i, mapEntry.wormhole, address(mapEntry.relayProvider)); mapEntry.coreRelayerFull = CoreRelayer(address(mapEntry.coreRelayer)); mapEntry.integration = new MockRelayerIntegration(address(mapEntry.wormhole), address(mapEntry.coreRelayer)); mapEntry.relayer = address(uint160(uint256(keccak256(abi.encodePacked(bytes("relayer"), i))))); @@ -306,7 +264,7 @@ contract TestCoreRelayer is Test { ) internal { bytes32 coreRelayerModule = 0x000000000000000000000000000000000000000000436f726552656c61796572; bytes memory message = - abi.encodePacked(coreRelayerModule, uint8(2), uint16(currentChainId), chainId, coreRelayerContractAddress); + abi.encodePacked(coreRelayerModule, uint8(2), currentChainId, chainId, coreRelayerContractAddress); IWormhole.VM memory preSignedMessage = IWormhole.VM({ version: 1, timestamp: uint32(block.timestamp), @@ -815,6 +773,11 @@ contract TestCoreRelayer is Test { CoreRelayer.RedeliveryByTxHashInstruction instruction; } + function invalidateVM(bytes memory message, WormholeSimulator simulator) internal { + change(message, message.length - 1); + simulator.invalidateVM(message); + } + function change(bytes memory message, uint256 index) internal { if (message[index] == 0x02) { message[index] = 0x04; @@ -899,7 +862,7 @@ contract TestCoreRelayer is Test { bytes memory fakeVM = abi.encodePacked(stack.originalDelivery.encodedVMs[2]); bytes memory correctVM = abi.encodePacked(stack.originalDelivery.encodedVMs[2]); - change(fakeVM, fakeVM.length - 1); + invalidateVM(fakeVM, setup.target.wormholeSimulator); stack.originalDelivery.encodedVMs[2] = fakeVM; stack.package = CoreRelayerStructs.TargetRedeliveryByTxHashParamsSingle( @@ -936,7 +899,7 @@ contract TestCoreRelayer is Test { correctVM = abi.encodePacked(stack.redeliveryVM); fakeVM = abi.encodePacked(correctVM); - change(fakeVM, fakeVM.length - 1); + invalidateVM(fakeVM, setup.target.wormholeSimulator); stack.package = CoreRelayerStructs.TargetRedeliveryByTxHashParamsSingle( fakeVM, stack.originalDelivery.encodedVMs, payable(setup.target.relayer) @@ -1207,7 +1170,7 @@ contract TestCoreRelayer is Test { bytes memory fakeVM = abi.encodePacked(stack.deliveryVM); - change(fakeVM, fakeVM.length - 1); + invalidateVM(fakeVM, setup.target.wormholeSimulator); stack.encodedVMs = new bytes[](3); stack.encodedVMs[0] = stack.actualVM1; diff --git a/ethereum/forge-test/WormholeSimulator.sol b/ethereum/forge-test/WormholeSimulator.sol index 1ce9e8d..1f870ba 100644 --- a/ethereum/forge-test/WormholeSimulator.sol +++ b/ethereum/forge-test/WormholeSimulator.sol @@ -2,106 +2,19 @@ pragma solidity ^0.8.0; import {IWormhole} from "../contracts/interfaces/IWormhole.sol"; +import {MockWormhole} from "../contracts/mock/MockWormhole.sol"; import "../contracts/libraries/external/BytesLib.sol"; import "forge-std/Vm.sol"; import "forge-std/console.sol"; /** - * @title A Wormhole Guardian Simulator - * @notice This contract simulates signing Wormhole messages emitted in a forge test. - * It overrides the Wormhole guardian set to allow for signing messages with a single - * private key on any EVM where Wormhole core contracts are deployed. + * @notice These are the common parts for the signing and the non signing wormhole simulators. * @dev This contract is meant to be used when testing against a mainnet fork. */ -contract WormholeSimulator { +abstract contract WormholeSimulator { using BytesLib for bytes; - // Taken from forge-std/Script.sol - address private constant VM_ADDRESS = address(bytes20(uint160(uint256(keccak256("hevm cheat code"))))); - Vm public constant vm = Vm(VM_ADDRESS); - - // Allow access to Wormhole - IWormhole public wormhole; - - // Save the guardian PK to sign messages with - uint256 private devnetGuardianPK; - - /** - * @param wormhole_ address of the Wormhole core contract for the mainnet chain being forked - * @param devnetGuardian private key of the devnet Guardian - */ - constructor(address wormhole_, uint256 devnetGuardian) { - wormhole = IWormhole(wormhole_); - devnetGuardianPK = devnetGuardian; - overrideToDevnetGuardian(vm.addr(devnetGuardian)); - } - - function overrideToDevnetGuardian(address devnetGuardian) internal { - { - bytes32 data = vm.load(address(this), bytes32(uint256(2))); - require(data == bytes32(0), "incorrect slot"); - - // Get slot for Guardian Set at the current index - uint32 guardianSetIndex = wormhole.getCurrentGuardianSetIndex(); - bytes32 guardianSetSlot = keccak256(abi.encode(guardianSetIndex, 2)); - - // Overwrite all but first guardian set to zero address. This isn't - // necessary, but just in case we inadvertently access these slots - // for any reason. - uint256 numGuardians = uint256(vm.load(address(wormhole), guardianSetSlot)); - for (uint256 i = 1; i < numGuardians;) { - vm.store( - address(wormhole), bytes32(uint256(keccak256(abi.encodePacked(guardianSetSlot))) + i), bytes32(0) - ); - unchecked { - i += 1; - } - } - - // Now overwrite the first guardian key with the devnet key specified - // in the function argument. - vm.store( - address(wormhole), - bytes32(uint256(keccak256(abi.encodePacked(guardianSetSlot))) + 0), // just explicit w/ index 0 - bytes32(uint256(uint160(devnetGuardian))) - ); - - // Change the length to 1 guardian - vm.store( - address(wormhole), - guardianSetSlot, - bytes32(uint256(1)) // length == 1 - ); - - // Confirm guardian set override - address[] memory guardians = wormhole.getGuardianSet(guardianSetIndex).keys; - require(guardians.length == 1, "guardians.length != 1"); - require(guardians[0] == devnetGuardian, "incorrect guardian set override"); - } - } - - function setMessageFee(uint256 newFee) public { - bytes32 coreModule = 0x00000000000000000000000000000000000000000000000000000000436f7265; - bytes memory message = abi.encodePacked(coreModule, uint8(3), uint16(wormhole.chainId()), newFee); - IWormhole.VM memory preSignedMessage = IWormhole.VM({ - version: 1, - timestamp: uint32(block.timestamp), - nonce: 0, - emitterChainId: wormhole.governanceChainId(), - emitterAddress: wormhole.governanceContract(), - sequence: 0, - consistencyLevel: 200, - payload: message, - guardianSetIndex: 0, - signatures: new IWormhole.Signature[](0), - hash: bytes32("") - }); - - bytes memory signed = encodeAndSignMessage(preSignedMessage); - wormhole.submitSetMessageFee(signed); - } - function doubleKeccak256(bytes memory body) internal pure returns (bytes32) { return keccak256(abi.encodePacked(keccak256(body))); } @@ -192,11 +105,8 @@ contract WormholeSimulator { public returns (bytes memory signedMessage) { - // Create message instance - IWormhole.VM memory vm_; - // Parse wormhole message from ethereum logs - vm_ = parseVMFromLogs(log); + IWormhole.VM memory vm_ = parseVMFromLogs(log); // Set empty body values before computing the hash vm_.version = uint8(1); @@ -207,6 +117,67 @@ contract WormholeSimulator { return encodeAndSignMessage(vm_); } + /** + * Functions that must be implemented by concrete wormhole simulators. + */ + + /** + * @notice Sets the message fee for a wormhole message. + */ + function setMessageFee(uint256 newFee) public virtual; + + /** + * @notice Invalidates a VM. It must be executed before it is parsed and verified by the Wormhole instance to work. + */ + function invalidateVM(bytes memory message) public virtual; + + /** + * @notice Formats and signs a simulated Wormhole batch VAA given an array of Wormhole log entries + * @param logs The forge Vm.log entries captured when recording events during test execution + * @param nonce The nonce of the messages to be accumulated into the batch VAA + * @return signedMessage Formatted and signed Wormhole message + */ + function fetchSignedBatchVAAFromLogs( + Vm.Log[] memory logs, + uint32 nonce, + uint16 emitterChainId, + address emitterAddress + ) public virtual returns (bytes memory signedMessage); + + /** + * @notice Signs and preformatted simulated Wormhole message + * @param vm_ The preformatted Wormhole message + * @return signedMessage Formatted and signed Wormhole message + */ + function encodeAndSignMessage(IWormhole.VM memory vm_) public virtual returns (bytes memory signedMessage); +} + +/** + * @title A Wormhole Guardian Simulator + * @notice This contract simulates signing Wormhole messages emitted in a forge test. + * This particular version doesn't sign any message but just exists to keep a standard interface for tests. + * @dev This contract is meant to be used with the MockWormhole contract that validates any VM as long + * as its hash wasn't banned. + */ +contract FakeWormholeSimulator is WormholeSimulator { + // Allow access to Wormhole + MockWormhole public wormhole; + + /** + * @param initWormhole address of the Wormhole core contract for the mainnet chain being forked + */ + constructor(MockWormhole initWormhole) { + wormhole = initWormhole; + } + + function setMessageFee(uint256 newFee) public override { + wormhole.setMessageFee(newFee); + } + + function invalidateVM(bytes memory message) public override { + wormhole.invalidateVM(message); + } + /** * @notice Formats and signs a simulated Wormhole batch VAA given an array of Wormhole log entries * @param logs The forge Vm.log entries captured when recording events during test execution @@ -218,7 +189,198 @@ contract WormholeSimulator { uint32 nonce, uint16 emitterChainId, address emitterAddress - ) public returns (bytes memory signedMessage) { + ) public override returns (bytes memory signedMessage) { + uint8 numObservations = 0; + IWormhole.VM[] memory vm_ = new IWormhole.VM[](logs.length); + + for (uint256 i = 0; i < logs.length; i++) { + vm_[i] = parseVMFromLogs(logs[i]); + vm_[i].timestamp = uint32(block.timestamp); + vm_[i].emitterChainId = emitterChainId; + vm_[i].emitterAddress = bytes32(uint256(uint160(emitterAddress))); + if (vm_[i].nonce == nonce) { + numObservations += 1; + } + } + + bytes memory packedObservations; + bytes32[] memory hashes = new bytes32[](numObservations); + + uint8 counter = 0; + for (uint256 i = 0; i < logs.length; i++) { + if (vm_[i].nonce == nonce) { + bytes memory observation = abi.encodePacked( + vm_[i].timestamp, + vm_[i].nonce, + vm_[i].emitterChainId, + vm_[i].emitterAddress, + vm_[i].sequence, + vm_[i].consistencyLevel, + vm_[i].payload + ); + hashes[counter] = doubleKeccak256(observation); + packedObservations = + abi.encodePacked(packedObservations, uint8(counter), uint32(observation.length), observation); + counter++; + } + } + + signedMessage = abi.encodePacked( + // vm version + uint8(2), + wormhole.getCurrentGuardianSetIndex(), + // length of signature array + uint8(1), + // guardian index + uint8(0), + // r sig argument + bytes32(uint256(0)), + // s sig argument + bytes32(uint256(0)), + // v sig argument (encodes public key recovery id, public key type and network of the signature) + uint8(0), + numObservations, + hashes, + numObservations, + packedObservations + ); + } + + /** + * @notice Signs and preformatted simulated Wormhole message + * @param vm_ The preformatted Wormhole message + * @return signedMessage Formatted and signed Wormhole message + */ + function encodeAndSignMessage(IWormhole.VM memory vm_) public override returns (bytes memory signedMessage) { + // Compute the hash of the body + bytes memory body = encodeObservation(vm_); + vm_.hash = doubleKeccak256(body); + + signedMessage = abi.encodePacked( + vm_.version, + wormhole.getCurrentGuardianSetIndex(), + // length of signature array + uint8(1), + // guardian index + uint8(0), + // r sig argument + bytes32(uint256(0)), + // s sig argument + bytes32(uint256(0)), + // v sig argument (encodes public key recovery id, public key type and network of the signature) + uint8(0), + body + ); + } +} + +/** + * @title A Wormhole Guardian Simulator + * @notice This contract simulates signing Wormhole messages emitted in a forge test. + * It overrides the Wormhole guardian set to allow for signing messages with a single + * private key on any EVM where Wormhole core contracts are deployed. + * @dev This contract is meant to be used when testing against a mainnet fork. + */ +contract SigningWormholeSimulator is WormholeSimulator { + // Taken from forge-std/Script.sol + address private constant VM_ADDRESS = address(bytes20(uint160(uint256(keccak256("hevm cheat code"))))); + Vm public constant vm = Vm(VM_ADDRESS); + + // Allow access to Wormhole + IWormhole public wormhole; + + // Save the guardian PK to sign messages with + uint256 private devnetGuardianPK; + + /** + * @param wormhole_ address of the Wormhole core contract for the mainnet chain being forked + * @param devnetGuardian private key of the devnet Guardian + */ + constructor(IWormhole wormhole_, uint256 devnetGuardian) { + wormhole = wormhole_; + devnetGuardianPK = devnetGuardian; + overrideToDevnetGuardian(vm.addr(devnetGuardian)); + } + + function overrideToDevnetGuardian(address devnetGuardian) internal { + { + // Get slot for Guardian Set at the current index + uint32 guardianSetIndex = wormhole.getCurrentGuardianSetIndex(); + bytes32 guardianSetSlot = keccak256(abi.encode(guardianSetIndex, 2)); + + // Overwrite all but first guardian set to zero address. This isn't + // necessary, but just in case we inadvertently access these slots + // for any reason. + uint256 numGuardians = uint256(vm.load(address(wormhole), guardianSetSlot)); + for (uint256 i = 1; i < numGuardians;) { + vm.store( + address(wormhole), bytes32(uint256(keccak256(abi.encodePacked(guardianSetSlot))) + i), bytes32(0) + ); + unchecked { + i += 1; + } + } + + // Now overwrite the first guardian key with the devnet key specified + // in the function argument. + vm.store( + address(wormhole), + bytes32(uint256(keccak256(abi.encodePacked(guardianSetSlot))) + 0), // just explicit w/ index 0 + bytes32(uint256(uint160(devnetGuardian))) + ); + + // Change the length to 1 guardian + vm.store( + address(wormhole), + guardianSetSlot, + bytes32(uint256(1)) // length == 1 + ); + + // Confirm guardian set override + address[] memory guardians = wormhole.getGuardianSet(guardianSetIndex).keys; + require(guardians.length == 1, "guardians.length != 1"); + require(guardians[0] == devnetGuardian, "incorrect guardian set override"); + } + } + + function setMessageFee(uint256 newFee) public override { + bytes32 coreModule = 0x00000000000000000000000000000000000000000000000000000000436f7265; + bytes memory message = abi.encodePacked(coreModule, uint8(3), uint16(wormhole.chainId()), newFee); + IWormhole.VM memory preSignedMessage = IWormhole.VM({ + version: 1, + timestamp: uint32(block.timestamp), + nonce: 0, + emitterChainId: wormhole.governanceChainId(), + emitterAddress: wormhole.governanceContract(), + sequence: 0, + consistencyLevel: 200, + payload: message, + guardianSetIndex: 0, + signatures: new IWormhole.Signature[](0), + hash: bytes32("") + }); + + bytes memory signed = encodeAndSignMessage(preSignedMessage); + wormhole.submitSetMessageFee(signed); + } + + function invalidateVM(bytes memory message) public pure override { + // Don't do anything. Signatures are easily invalidated modifying the payload. + // If it becomes necessary to prevent producing a good signature for this message, that can be done here. + } + + /** + * @notice Formats and signs a simulated Wormhole batch VAA given an array of Wormhole log entries + * @param logs The forge Vm.log entries captured when recording events during test execution + * @param nonce The nonce of the messages to be accumulated into the batch VAA + * @return signedMessage Formatted and signed Wormhole message + */ + function fetchSignedBatchVAAFromLogs( + Vm.Log[] memory logs, + uint32 nonce, + uint16 emitterChainId, + address emitterAddress + ) public override returns (bytes memory signedMessage) { uint8 numObservations = 0; IWormhole.VM[] memory vm_ = new IWormhole.VM[](logs.length); @@ -281,7 +443,7 @@ contract WormholeSimulator { * @param vm_ The preformatted Wormhole message * @return signedMessage Formatted and signed Wormhole message */ - function encodeAndSignMessage(IWormhole.VM memory vm_) public returns (bytes memory signedMessage) { + function encodeAndSignMessage(IWormhole.VM memory vm_) public override returns (bytes memory signedMessage) { // Compute the hash of the body bytes memory body = encodeObservation(vm_); vm_.hash = doubleKeccak256(body);