Adds `MockWormhole` contract for unit tests.

Also adds a `WormholeSimulator` interface for signing and non-signing
mocks. The signing mock can be useful when writing a test for a
specific testnet fork with a real wormhole deployment. In the unit tests,
only the non signing mock is used.
This commit is contained in:
Sebastián Claudio Nale 2023-02-13 12:53:32 -03:00 committed by scnale
parent 45021b4691
commit 3f713d7efc
3 changed files with 568 additions and 162 deletions

View File

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

View File

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

View File

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