Skip to content
This repository has been archived by the owner on May 23, 2023. It is now read-only.

Commit

Permalink
Improve forge test coverage + Seperate tests into individual tests (#119
Browse files Browse the repository at this point in the history
)

* fast tests

* forge fmt

* Remove parameter from forge test generic relayer

* WIP

* WIP

* Move Forge Mock Generic Relayer to seperate file

* Split revert redelivery tests into first few seperate tests

* redelivery test changes

* WIP

* tests pass

* Redelivery tests less D.R.Y and seperated into individual tests

* Delivery tests less D.R.Y and seperated into individual tests

* Resend checks in seperate tests

* Forward test coverage!

* 100% test coverage in CoreRelayer and CoreRelayerDelivery!

* remove test coverage files

* Governance tests

* governance stack

* forge fmt

* removed console.sol
  • Loading branch information
derpy-duck authored Mar 23, 2023
1 parent 48e2061 commit 9e28250
Show file tree
Hide file tree
Showing 13 changed files with 1,693 additions and 641 deletions.
1 change: 0 additions & 1 deletion ethereum/contracts/coreRelayer/CoreRelayer.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// contracts/Bridge.sol
// SPDX-License-Identifier: Apache 2

pragma solidity ^0.8.0;
Expand Down
28 changes: 16 additions & 12 deletions ethereum/contracts/coreRelayer/CoreRelayerDelivery.sol
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,8 @@ contract CoreRelayerDelivery is CoreRelayerGovernance {
// - the permissioned address allowed to execute this redelivery instruction is the permissioned address allowed to execute the old instruction
valid = checkRedeliveryInstructionTarget(redeliveryInstruction, originalInstruction);

// Emit an 'Invalid Redelivery' event if one of the following five checks failed:
// - msg.sender is the permissioned address allowed to execute this redelivery instruction
// - the redelivery instruction's target chain = this chain
// Emit an 'Invalid Redelivery' event if one of the following four checks failed:
// - the permissioned address allowed to execute this redelivery instruction is the permissioned address allowed to execute the old instruction
// - the original instruction's target chain = this chain
// - the new redelivery instruction's 'receiver value' amount >= the original instruction's 'receiver value' amount
// - the new redelivery instruction's upper bound on gas >= the original instruction's upper bound on gas
Expand Down Expand Up @@ -345,9 +344,16 @@ contract CoreRelayerDelivery is CoreRelayerGovernance {
) internal view returns (bool isValid) {
address providerAddress = fromWormholeFormat(redeliveryInstruction.executionParameters.providerDeliveryAddress);

// Check that the permissioned address allowed to execute this redelivery instruction is the permissioned address allowed to execute the old instruction
if ((providerAddress != fromWormholeFormat(originalInstruction.executionParameters.providerDeliveryAddress))) {
revert IDelivery.MismatchingRelayProvidersInRedelivery();
// Check that msg.sender is the permissioned address allowed to execute this redelivery instruction
if (providerAddress != msg.sender) {
revert IDelivery.UnexpectedRelayer();
}

uint16 whChainId = chainId();

// Check that the redelivery instruction's target chain = this chain
if (whChainId != redeliveryInstruction.targetChain) {
revert IDelivery.TargetChainIsNotThisChain(redeliveryInstruction.targetChain);
}

uint256 wormholeMessageFee = wormhole().messageFee();
Expand All @@ -361,12 +367,10 @@ contract CoreRelayerDelivery is CoreRelayerGovernance {
revert IDelivery.InsufficientRelayerFunds();
}

uint16 whChainId = chainId();

// Check that msg.sender is the permissioned address allowed to execute this redelivery instruction
isValid = msg.sender == providerAddress
// Check that the redelivery instruction's target chain = this chain
&& whChainId == redeliveryInstruction.targetChain
// Check that the permissioned address allowed to execute this redelivery instruction is the permissioned address allowed to execute the old instruction
isValid = (
providerAddress == fromWormholeFormat(originalInstruction.executionParameters.providerDeliveryAddress)
)
// Check that the original instruction's target chain = this chain
&& whChainId == originalInstruction.targetChain
// Check that the new redelivery instruction's 'receiver value' amount >= the original instruction's 'receiver value' amount
Expand Down
1 change: 0 additions & 1 deletion ethereum/contracts/interfaces/IDelivery.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// contracts/Messages.sol
// SPDX-License-Identifier: Apache 2

pragma solidity ^0.8.0;
Expand Down
1 change: 0 additions & 1 deletion ethereum/contracts/interfaces/IRelayProvider.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// contracts/Messages.sol
// SPDX-License-Identifier: Apache 2

pragma solidity ^0.8.0;
Expand Down
1 change: 0 additions & 1 deletion ethereum/contracts/interfaces/IWormhole.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// contracts/Messages.sol
// SPDX-License-Identifier: Apache 2

pragma solidity ^0.8.0;
Expand Down
1 change: 0 additions & 1 deletion ethereum/contracts/interfaces/IWormholeReceiver.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// contracts/Messages.sol
// SPDX-License-Identifier: Apache 2

pragma solidity ^0.8.0;
Expand Down
2 changes: 1 addition & 1 deletion ethereum/contracts/mock/MockRelayerIntegration.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
122 changes: 122 additions & 0 deletions ethereum/forge-test/ForwardTester.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "../contracts/interfaces/IWormhole.sol";
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;

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
}

function receiveWormholeMessages(bytes[] memory vaas, bytes[] memory additionalData) public payable override {
(IWormhole.VM memory vaa, bool valid, string memory reason) = wormhole.parseAndVerifyVM(vaas[0]);
require(valid, reason);

bytes memory payload = vaa.payload;
Action action = Action(payload.toUint8(0));

if (action == Action.MultipleForwardsRequested) {
uint256 maxTransactionFee =
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());
wormholeRelayer.forward(vaa.emitterChainId, vaa.emitterAddress, vaa.emitterAddress, maxTransactionFee, 0, 0);
} else if (action == Action.MultichainSendEmpty) {
wormholeRelayer.multichainForward(
IWormholeRelayer.MultichainSend(
wormholeRelayer.getDefaultRelayProvider(), new IWormholeRelayer.Send[](0)
),
1
);
} else if (action == Action.MaxTransactionFeeNotEnough) {
uint256 maxTransactionFee =
wormholeRelayer.quoteGas(vaa.emitterChainId, 1, wormholeRelayer.getDefaultRelayProvider()) - 1;
wormholeRelayer.forward(vaa.emitterChainId, vaa.emitterAddress, vaa.emitterAddress, maxTransactionFee, 0, 1);
} else if (action == Action.FundsTooMuch) {
// set maximum budget to less than this
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);
}
}
53 changes: 53 additions & 0 deletions ethereum/forge-test/IWormholeRelayerInstructionParser.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: Apache 2

pragma solidity ^0.8.0;

interface IWormholeRelayerInstructionParser {
struct DeliveryInstructionsContainer {
uint8 payloadId; //1
bool sufficientlyFunded;
DeliveryInstruction[] instructions;
}

struct DeliveryInstruction {
uint16 targetChain;
bytes32 targetAddress;
bytes32 refundAddress;
uint256 maximumRefundTarget;
uint256 receiverValueTarget;
ExecutionParameters executionParameters;
}

struct ExecutionParameters {
uint8 version;
uint32 gasLimit;
bytes32 providerDeliveryAddress;
}

struct RedeliveryByTxHashInstruction {
uint8 payloadId; //2
uint16 sourceChain;
bytes32 sourceTxHash;
uint32 sourceNonce;
uint16 targetChain;
uint8 deliveryIndex;
uint8 multisendIndex;
uint256 newMaximumRefundTarget;
uint256 newReceiverValueTarget;
ExecutionParameters executionParameters;
}

function decodeDeliveryInstructionsContainer(bytes memory encoded)
external
pure
returns (DeliveryInstructionsContainer memory);

function decodeRedeliveryInstruction(bytes memory encoded)
external
pure
returns (RedeliveryByTxHashInstruction memory instruction);

function toWormholeFormat(address addr) external pure returns (bytes32 whFormat);

function fromWormholeFormat(bytes32 whFormatAddress) external pure returns (address addr);
}
Loading

0 comments on commit 9e28250

Please sign in to comment.