100% test coverage in CoreRelayer and CoreRelayerDelivery!
This commit is contained in:
parent
3f679c64a7
commit
a7f563e949
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue