350 lines
12 KiB
Solidity
350 lines
12 KiB
Solidity
// SPDX-License-Identifier: Apache 2
|
|
pragma solidity ^0.8.0;
|
|
|
|
import {IWormhole} from "../../contracts/interfaces/IWormhole.sol";
|
|
import {MockWormhole} from "./MockWormhole.sol";
|
|
import "../../contracts/libraries/external/BytesLib.sol";
|
|
|
|
import "forge-std/Vm.sol";
|
|
import "forge-std/console.sol";
|
|
|
|
/**
|
|
* @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.
|
|
*/
|
|
abstract contract WormholeSimulator {
|
|
using BytesLib for bytes;
|
|
|
|
function doubleKeccak256(bytes memory body) internal pure returns (bytes32) {
|
|
return keccak256(abi.encodePacked(keccak256(body)));
|
|
}
|
|
|
|
function parseVMFromLogs(Vm.Log memory log) public pure returns (IWormhole.VM memory vm_) {
|
|
uint256 index = 0;
|
|
|
|
// emitterAddress
|
|
vm_.emitterAddress = bytes32(log.topics[1]);
|
|
|
|
// sequence
|
|
vm_.sequence = log.data.toUint64(index + 32 - 8);
|
|
index += 32;
|
|
|
|
// nonce
|
|
vm_.nonce = log.data.toUint32(index + 32 - 4);
|
|
index += 32;
|
|
|
|
// skip random bytes
|
|
index += 32;
|
|
|
|
// consistency level
|
|
vm_.consistencyLevel = log.data.toUint8(index + 32 - 1);
|
|
index += 32;
|
|
|
|
// length of payload
|
|
uint256 payloadLen = log.data.toUint256(index);
|
|
index += 32;
|
|
|
|
vm_.payload = log.data.slice(index, payloadLen);
|
|
index += payloadLen;
|
|
|
|
// trailing bytes (due to 32 byte slot overlap)
|
|
index += log.data.length - index;
|
|
|
|
require(index == log.data.length, "failed to parse wormhole message");
|
|
}
|
|
|
|
/**
|
|
* @notice Finds published Wormhole events in forge logs
|
|
* @param logs The forge Vm.log captured when recording events during test execution
|
|
*/
|
|
function fetchWormholeMessageFromLog(Vm.Log[] memory logs)
|
|
public
|
|
pure
|
|
returns (Vm.Log[] memory)
|
|
{
|
|
uint256 count = 0;
|
|
for (uint256 i = 0; i < logs.length; i++) {
|
|
if (
|
|
logs[i].topics[0]
|
|
== keccak256("LogMessagePublished(address,uint64,uint32,bytes,uint8)")
|
|
) {
|
|
count += 1;
|
|
}
|
|
}
|
|
|
|
// create log array to save published messages
|
|
Vm.Log[] memory published = new Vm.Log[](count);
|
|
|
|
uint256 publishedIndex = 0;
|
|
for (uint256 i = 0; i < logs.length; i++) {
|
|
if (
|
|
logs[i].topics[0]
|
|
== keccak256("LogMessagePublished(address,uint64,uint32,bytes,uint8)")
|
|
) {
|
|
published[publishedIndex] = logs[i];
|
|
publishedIndex += 1;
|
|
}
|
|
}
|
|
|
|
return published;
|
|
}
|
|
|
|
/**
|
|
* @notice Encodes Wormhole message body into bytes
|
|
* @param vm_ Wormhole VM struct
|
|
* @return encodedObservation Wormhole message body encoded into bytes
|
|
*/
|
|
function encodeObservation(IWormhole.VM memory vm_)
|
|
public
|
|
pure
|
|
returns (bytes memory encodedObservation)
|
|
{
|
|
encodedObservation = abi.encodePacked(
|
|
vm_.timestamp,
|
|
vm_.nonce,
|
|
vm_.emitterChainId,
|
|
vm_.emitterAddress,
|
|
vm_.sequence,
|
|
vm_.consistencyLevel,
|
|
vm_.payload
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @notice Formats and signs a simulated Wormhole message using the emitted log from calling `publishMessage`
|
|
* @param log The forge Vm.log captured when recording events during test execution
|
|
* @return signedMessage Formatted and signed Wormhole message
|
|
*/
|
|
function fetchSignedMessageFromLogs(
|
|
Vm.Log memory log,
|
|
uint16 emitterChainId,
|
|
address emitterAddress
|
|
) public returns (bytes memory signedMessage) {
|
|
// Parse wormhole message from ethereum logs
|
|
IWormhole.VM memory vm_ = parseVMFromLogs(log);
|
|
|
|
// Set empty body values before computing the hash
|
|
vm_.version = uint8(1);
|
|
vm_.timestamp = uint32(block.timestamp);
|
|
vm_.emitterChainId = emitterChainId;
|
|
vm_.emitterAddress = bytes32(uint256(uint160(emitterAddress)));
|
|
|
|
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 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 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
|
|
view
|
|
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 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
|
|
view
|
|
override
|
|
returns (bytes memory signedMessage)
|
|
{
|
|
// Compute the hash of the body
|
|
bytes memory body = encodeObservation(vm_);
|
|
vm_.hash = doubleKeccak256(body);
|
|
|
|
// Sign the hash with the devnet guardian private key
|
|
IWormhole.Signature[] memory sigs = new IWormhole.Signature[](1);
|
|
(sigs[0].v, sigs[0].r, sigs[0].s) = vm.sign(devnetGuardianPK, vm_.hash);
|
|
sigs[0].guardianIndex = 0;
|
|
|
|
signedMessage = abi.encodePacked(
|
|
vm_.version,
|
|
wormhole.getCurrentGuardianSetIndex(),
|
|
uint8(sigs.length),
|
|
sigs[0].guardianIndex,
|
|
sigs[0].r,
|
|
sigs[0].s,
|
|
sigs[0].v - 27,
|
|
body
|
|
);
|
|
}
|
|
}
|