100% test coverage in CoreRelayer and CoreRelayerDelivery!

This commit is contained in:
derpy-duck 2023-03-15 17:55:17 +00:00
parent 3f679c64a7
commit a7f563e949
7 changed files with 12763 additions and 9 deletions

View File

@ -7,6 +7,7 @@ import "../interfaces/IWormholeReceiver.sol";
import "../interfaces/IDelivery.sol";
import "./CoreRelayerGovernance.sol";
import "./CoreRelayerStructs.sol";
import "forge-std/console.sol";
contract CoreRelayerDelivery is CoreRelayerGovernance {
enum DeliveryStatus {

View File

@ -153,7 +153,7 @@ contract MockRelayerIntegration is IWormholeReceiver {
(IWormhole.VM memory parsed, bool valid, string memory reason) =
wormhole.parseAndVerifyVM(wormholeObservations[i]);
require(valid, reason);
require(registeredContracts[parsed.emitterChainId] == parsed.emitterAddress);
require(registeredContracts[parsed.emitterChainId] == parsed.emitterAddress, "Emitter address not valid");
emitterChainId = parsed.emitterChainId;
messages[i] = parsed.payload;
}

View File

@ -304,9 +304,13 @@ contract TestCoreRelayer is Test {
vaaHash = vm.getRecordedLogs()[0].data.toBytes32(0);
}
function getDeliveryStatus(Vm.Log memory log) internal returns (DeliveryStatus status) {
status = DeliveryStatus(log.data.toUint256(32));
}
function getDeliveryStatus() internal returns (DeliveryStatus status) {
Vm.Log[] memory logs = vm.getRecordedLogs();
status = DeliveryStatus(logs[logs.length - 1].data.toUint256(32));
status = getDeliveryStatus(logs[logs.length - 1]);
}
function testSend(GasParameters memory gasParams, FeeParameters memory feeParams, bytes memory message) public {
@ -418,9 +422,6 @@ contract TestCoreRelayer is Test {
uint256 relayerProfit = uint256(feeParams.sourceNativePrice)
* (setup.source.rewardAddress.balance - rewardAddressBalance)
- feeParams.targetNativePrice * (relayerBalance - setup.target.relayer.balance);
console.log(USDcost);
console.log(relayerProfit);
console.log((USDcost - relayerProfit));
assertTrue(USDcost == relayerProfit, "We paid the exact amount");
}
@ -479,6 +480,61 @@ contract TestCoreRelayer is Test {
assertTrue(keccak256(setup.source.integration.getMessage()) == keccak256(bytes("received!")));
}
function testForwardRequestFail(
GasParameters memory gasParams,
FeeParameters memory feeParams,
bytes memory message
) public {
StandardSetupTwoChains memory setup = standardAssumeAndSetupTwoChains(gasParams, feeParams, 1000000);
vm.assume(
uint256(1) * feeParams.targetNativePrice * gasParams.targetGasPrice * 10
< uint256(1) * feeParams.sourceNativePrice * gasParams.sourceGasPrice
);
uint256 payment =
setup.source.coreRelayer.quoteGas(setup.targetChainId, 1000000, address(setup.source.relayProvider));
vm.recordLogs();
IWormhole wormhole = setup.source.wormhole;
uint256 wormholeFee = wormhole.messageFee();
vm.prank(address(setup.source.integration));
wormhole.publishMessage{value: wormholeFee}(1, message, 200);
uint16[] memory chains = new uint16[](1);
chains[0] = wormhole.chainId();
uint32[] memory gasLimits = new uint32[](1);
gasLimits[0] = 1000000;
bytes[] memory newMessages = new bytes[](2);
newMessages[0] = bytes("received!");
newMessages[1] = abi.encodePacked(uint8(0));
MockRelayerIntegration.FurtherInstructions memory instructions = MockRelayerIntegration.FurtherInstructions({
keepSending: true,
newMessages: newMessages,
chains: chains,
gasLimits: gasLimits
});
vm.prank(address(setup.source.integration));
wormhole.publishMessage{value: wormholeFee}(
1, setup.source.integration.encodeFurtherInstructions(instructions), 200
);
bytes32 targetAddress = setup.source.coreRelayer.toWormholeFormat(address(setup.target.integration));
setup.source.coreRelayer.send{value: payment + wormholeFee}(
setup.targetChainId, targetAddress, targetAddress, payment, 0, 1
);
genericRelayer.relay(setup.sourceChainId);
assertTrue(keccak256(setup.target.integration.getMessage()) == keccak256(message));
Vm.Log[] memory logs = vm.getRecordedLogs();
genericRelayer.relay(logs, setup.targetChainId);
assertTrue(keccak256(setup.source.integration.getMessage()) != keccak256(bytes("received!")));
assertTrue(getDeliveryStatus(logs[logs.length - 1]) == DeliveryStatus.FORWARD_REQUEST_FAILURE);
}
function testAttackForwardRequestCache(GasParameters memory gasParams, FeeParameters memory feeParams) public {
// General idea:
// 1. Attacker sets up a malicious integration contract in the target chain.
@ -1727,7 +1783,8 @@ contract TestCoreRelayer is Test {
) internal {
ForwardStack memory stack;
vm.recordLogs();
forwardTester = new ForwardTester(address(setup.target.wormhole), address(setup.target.coreRelayer));
forwardTester = new ForwardTester(address(setup.target.wormhole), address(setup.target.coreRelayer), address(setup.target.wormholeSimulator));
vm.deal(address(forwardTester), type(uint256).max/2);
stack.targetAddress = setup.source.coreRelayer.toWormholeFormat(address(forwardTester));
stack.payment = assumeAndGetForwardPayment(gasParams.targetGasLimit, 500000, setup, gasParams, feeParams);
stack.wormholeFee = setup.source.wormhole.messageFee();
@ -1785,6 +1842,24 @@ contract TestCoreRelayer is Test {
);
}
function testRevertForwardForwardRequestFromWrongAddress(
GasParameters memory gasParams,
FeeParameters memory feeParams
) public {
StandardSetupTwoChains memory setup = standardAssumeAndSetupTwoChains(gasParams, feeParams, 1000000);
executeForwardTest(
ForwardTester.Action.ForwardRequestFromWrongAddress, DeliveryStatus.RECEIVER_FAILURE, setup, gasParams, feeParams
);
}
function testRevertDeliveryReentrantCall(GasParameters memory gasParams, FeeParameters memory feeParams) public {
StandardSetupTwoChains memory setup = standardAssumeAndSetupTwoChains(gasParams, feeParams, 1000000);
executeForwardTest(
ForwardTester.Action.ReentrantCall, DeliveryStatus.RECEIVER_FAILURE, setup, gasParams, feeParams
);
}
function testRevertForwardMaxTransactionFeeNotEnough(GasParameters memory gasParams, FeeParameters memory feeParams)
public
{
@ -1840,4 +1915,10 @@ contract TestCoreRelayer is Test {
message, setup.targetChainId, address(setup.target.integration), address(setup.target.refundAddress)
);
}
function testToAndFromWormholeFormat(bytes32 msg2, address msg1) public {
assertTrue(map[1].coreRelayer.fromWormholeFormat(msg2) == address(uint160(uint256(msg2))));
assertTrue(map[1].coreRelayer.toWormholeFormat(msg1) == bytes32(uint256(uint160(msg1))));
assertTrue(map[1].coreRelayer.fromWormholeFormat(map[1].coreRelayer.toWormholeFormat(msg1)) == msg1);
}
}

View File

@ -6,24 +6,38 @@ import "../contracts/interfaces/IWormholeReceiver.sol";
import "../contracts/interfaces/IWormholeRelayer.sol";
import "../contracts/interfaces/IRelayProvider.sol";
import "../contracts/libraries/external/BytesLib.sol";
import "./MockGenericRelayer.sol";
import "forge-std/console.sol";
import "forge-std/Vm.sol";
contract ForwardTester is IWormholeReceiver {
using BytesLib for bytes;
IWormhole wormhole;
IWormholeRelayer wormholeRelayer;
MockGenericRelayer genericRelayer;
constructor(address _wormhole, address _wormholeRelayer) {
address private constant VM_ADDRESS = address(bytes20(uint160(uint256(keccak256("hevm cheat code")))));
Vm public constant vm = Vm(VM_ADDRESS);
constructor(address _wormhole, address _wormholeRelayer, address _wormholeSimulator) {
wormhole = IWormhole(_wormhole);
wormholeRelayer = IWormholeRelayer(_wormholeRelayer);
genericRelayer = new MockGenericRelayer(_wormhole, _wormholeSimulator, _wormholeRelayer);
genericRelayer.setWormholeRelayerContract(wormhole.chainId(), address(wormholeRelayer));
genericRelayer.setProviderDeliveryAddress(wormhole.chainId(), wormholeRelayer.fromWormholeFormat(IRelayProvider(wormholeRelayer.getDefaultRelayProvider()).getDeliveryAddress(wormhole.chainId())));
genericRelayer.setWormholeFee(wormhole.chainId(), wormhole.messageFee());
}
enum Action {
MultipleForwardsRequested,
ForwardRequestFromWrongAddress,
NonceIsZero,
MultichainSendEmpty,
MaxTransactionFeeNotEnough,
FundsTooMuch,
ReentrantCall,
WorksCorrectly
}
@ -39,6 +53,14 @@ contract ForwardTester is IWormholeReceiver {
wormholeRelayer.quoteGas(vaa.emitterChainId, 10000, wormholeRelayer.getDefaultRelayProvider());
wormholeRelayer.forward(vaa.emitterChainId, vaa.emitterAddress, vaa.emitterAddress, maxTransactionFee, 0, 1);
wormholeRelayer.forward(vaa.emitterChainId, vaa.emitterAddress, vaa.emitterAddress, maxTransactionFee, 0, 1);
} else if (action == Action.ForwardRequestFromWrongAddress) {
// Emitter must be a wormhole relayer
uint256 maxTransactionFee =
wormholeRelayer.quoteGas(vaa.emitterChainId, 10000, wormholeRelayer.getDefaultRelayProvider());
DummyContract dc = new DummyContract(address(wormholeRelayer));
dc.forward(
vaa.emitterChainId, vaa.emitterAddress, vaa.emitterAddress, maxTransactionFee, 0, 1
);
} else if (action == Action.NonceIsZero) {
uint256 maxTransactionFee =
wormholeRelayer.quoteGas(vaa.emitterChainId, 10000, wormholeRelayer.getDefaultRelayProvider());
@ -59,10 +81,31 @@ contract ForwardTester is IWormholeReceiver {
uint256 maxTransactionFee =
wormholeRelayer.quoteGas(vaa.emitterChainId, 10000, wormholeRelayer.getDefaultRelayProvider());
wormholeRelayer.forward(vaa.emitterChainId, vaa.emitterAddress, vaa.emitterAddress, maxTransactionFee, 0, 1);
} else if (action == Action.ReentrantCall) {
uint256 maxTransactionFee =
wormholeRelayer.quoteGas(wormhole.chainId(), 10000, wormholeRelayer.getDefaultRelayProvider());
vm.recordLogs();
wormholeRelayer.send{value: maxTransactionFee + wormhole.messageFee()}(wormhole.chainId(), vaa.emitterAddress, vaa.emitterAddress, maxTransactionFee, 0, 1);
genericRelayer.relay(wormhole.chainId());
} else {
uint256 maxTransactionFee =
wormholeRelayer.quoteGas(vaa.emitterChainId, 10000, wormholeRelayer.getDefaultRelayProvider());
wormholeRelayer.forward(vaa.emitterChainId, vaa.emitterAddress, vaa.emitterAddress, maxTransactionFee, 0, 1);
}
}
receive() external payable {}
}
contract DummyContract {
IWormholeRelayer wormholeRelayer;
constructor(address _wormholeRelayer) {
wormholeRelayer = IWormholeRelayer(_wormholeRelayer);
}
function forward(uint16 chainId, bytes32 targetAddress, bytes32 refundAddress, uint256 maxTransactionFee, uint256 receiverValue, uint32 nonce) public {
wormholeRelayer.forward(chainId, targetAddress, refundAddress, maxTransactionFee, receiverValue, nonce);
}
}

View File

@ -9,6 +9,7 @@ import {IWormhole} from "../contracts/interfaces/IWormhole.sol";
import {WormholeSimulator} from "./WormholeSimulator.sol";
import "../contracts/libraries/external/BytesLib.sol";
import "forge-std/Vm.sol";
import "forge-std/console.sol";
contract MockGenericRelayer {
using BytesLib for bytes;
@ -56,7 +57,11 @@ contract MockGenericRelayer {
}
function relay(uint16 chainId) public {
Vm.Log[] memory entries = relayerWormholeSimulator.fetchWormholeMessageFromLog(vm.getRecordedLogs());
relay(vm.getRecordedLogs(), chainId);
}
function relay(Vm.Log[] memory logs, uint16 chainId) public {
Vm.Log[] memory entries = relayerWormholeSimulator.fetchWormholeMessageFromLog(logs);
bytes[] memory encodedVMs = new bytes[](entries.length);
for (uint256 i = 0; i < encodedVMs.length; i++) {
encodedVMs[i] = relayerWormholeSimulator.fetchSignedMessageFromLogs(
@ -67,7 +72,6 @@ contract MockGenericRelayer {
for (uint16 i = 0; i < encodedVMs.length; i++) {
parsed[i] = relayerWormhole.parseVM(encodedVMs[i]);
}
for (uint16 i = 0; i < encodedVMs.length; i++) {
if (!nonceCompleted[parsed[i].nonce]) {
nonceCompleted[parsed[i].nonce] = true;

1889
ethereum/lcov.info Normal file

File diff suppressed because it is too large Load Diff

10736
ethereum/out.txt Normal file

File diff suppressed because it is too large Load Diff