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:
parent
45021b4691
commit
3f713d7efc
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue