diff --git a/audits/internal9/README.md b/audits/internal9/README.md
index 6c65e94..ad8e4e0 100644
--- a/audits/internal9/README.md
+++ b/audits/internal9/README.md
@@ -20,6 +20,7 @@ File | % Stmts | % Branch | % Funcs | % Lines |Uncovere
WormholeMessenger.sol | 0 | 0 | 0 | 0 |... 201,202,207 |
```
Pay attention, please! No tests for contract on scope.
+[x] Fixed
#### Wormhole security list
https://docs.wormhole.com/wormhole/quick-start/cross-chain-dev/standard-relayer#other-considerations
@@ -35,6 +36,7 @@ Refunding overpayment of gasLimit - [?] - requires discussion
Refunding overpayment of value sent - [?] - requires discussion
ref: https://github.com/wormhole-foundation/hello-wormhole/blob/main/src/extensions/HelloWormholeRefunds.sol
```
+[x] Good point, need to set up these parameters and call the corresponding function on the L1 Relayer (added to test scripts)
### Security issues
Details in [slither_full](https://github.com/valory-xyz/autonolas-governance/blob/main/audits/internal9/analysis/slither_full.txt)
@@ -56,6 +58,8 @@ so avoid casting bytes32 to address EVM.
}
ref: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/Base.sol#L30C52-L30C74 SDK of Wormhole
```
+[x] Fixed
+
##### Re-design contracts
Part of the code is common and has the nature of a repeatable pattern for all contracts.
It makes sense to separate it into a separate contract and replace magic numbers (20,12,4) with constants.
@@ -109,6 +113,7 @@ It makes sense to separate it into a separate contract and replace magic numbers
}
}
```
+[x] Fixed
diff --git a/contracts/bridges/BridgeMessenger.sol b/contracts/bridges/BridgeMessenger.sol
index 55d8f0d..469a6b8 100644
--- a/contracts/bridges/BridgeMessenger.sol
+++ b/contracts/bridges/BridgeMessenger.sol
@@ -9,39 +9,16 @@ import {IBridgeErrors} from "../interfaces/IBridgeErrors.sol";
/// @author Mariapia Moscatiello -
abstract contract BridgeMessenger is IBridgeErrors {
event FundsReceived(address indexed sender, uint256 value);
- event SourceGovernorUpdated(address indexed sourceGovernor);
// Default payload data length includes the number of bytes of at least one address (20 bytes or 160 bits),
// value (12 bytes or 96 bits) and the payload size (4 bytes or 32 bits)
uint256 public constant DEFAULT_DATA_LENGTH = 36;
- // Source governor address on L1 that is authorized to propagate the transaction execution across the bridge
- address public sourceGovernor;
/// @dev Receives native network token.
receive() external payable {
emit FundsReceived(msg.sender, msg.value);
}
- /// @dev Changes the source governor address (original Timelock).
- /// @notice The only way to change the source governor address is by the Timelock on L1 to request that change.
- /// This triggers a self-contract transaction of BridgeMessenger that changes the source governor address.
- /// @param newSourceGovernor New source governor address.
- function changeSourceGovernor(address newSourceGovernor) external virtual {
- // Check if the change is authorized by the previous governor itself
- // This is possible only if all the checks in the message process function pass and the contract calls itself
- if (msg.sender != address(this)) {
- revert SelfCallOnly(msg.sender, address(this));
- }
-
- // Check for the zero address
- if (newSourceGovernor == address(0)) {
- revert ZeroAddress();
- }
-
- sourceGovernor = newSourceGovernor;
- emit SourceGovernorUpdated(newSourceGovernor);
- }
-
/// @dev Processes received data.
/// @param data Bytes message sent from L2 Wormhole Relayer contract. The data must be encoded as a set of
/// continuous transactions packed into a single buffer, where each transaction is composed as follows:
diff --git a/contracts/bridges/OptimismMessenger.sol b/contracts/bridges/OptimismMessenger.sol
index 9fd2ec3..3bc1292 100644
--- a/contracts/bridges/OptimismMessenger.sol
+++ b/contracts/bridges/OptimismMessenger.sol
@@ -13,10 +13,13 @@ interface ICrossDomainMessenger {
/// @author Andrey Lebedev -
/// @author Mariapia Moscatiello -
contract OptimismMessenger is BridgeMessenger {
+ event SourceGovernorUpdated(address indexed sourceGovernor);
event MessageReceived(address indexed sourceMessageSender, bytes data);
// CDM Contract Proxy (Home) address on L2 that receives the message across the bridge from the source L1 network
address public immutable CDMContractProxyHome;
+ // Source governor address on L1 that is authorized to propagate the transaction execution across the bridge
+ address public sourceGovernor;
/// @dev OptimismMessenger constructor.
/// @param _CDMContractProxyHome CDM Contract Proxy (Home) address (Optimism).
@@ -31,6 +34,26 @@ contract OptimismMessenger is BridgeMessenger {
sourceGovernor = _sourceGovernor;
}
+ /// @dev Changes the source governor address (original Timelock).
+ /// @notice The only way to change the source governor address is by the Timelock on L1 to request that change.
+ /// This triggers a self-contract transaction of BridgeMessenger that changes the source governor address.
+ /// @param newSourceGovernor New source governor address.
+ function changeSourceGovernor(address newSourceGovernor) external virtual {
+ // Check if the change is authorized by the previous governor itself
+ // This is possible only if all the checks in the message process function pass and the contract calls itself
+ if (msg.sender != address(this)) {
+ revert SelfCallOnly(msg.sender, address(this));
+ }
+
+ // Check for the zero address
+ if (newSourceGovernor == address(0)) {
+ revert ZeroAddress();
+ }
+
+ sourceGovernor = newSourceGovernor;
+ emit SourceGovernorUpdated(newSourceGovernor);
+ }
+
/// @dev Processes a message received from the CDM Contract Proxy (Home) contract.
/// @notice The sender must be the Source Governor address (Timelock).
/// @param data Bytes message sent from the CDM Contract Proxy (Home) contract. The data must be encoded as a set of
diff --git a/contracts/bridges/WormholeMessenger.sol b/contracts/bridges/WormholeMessenger.sol
index 1828882..015939f 100644
--- a/contracts/bridges/WormholeMessenger.sol
+++ b/contracts/bridges/WormholeMessenger.sol
@@ -8,27 +8,30 @@ import {BridgeMessenger} from "./BridgeMessenger.sol";
/// @author Andrey Lebedev -
/// @author Mariapia Moscatiello -
contract WormholeMessenger is BridgeMessenger {
- event MessageReceived(address indexed sourceMessageSender, bytes data, bytes32 deliveryHash, uint256 sourceChain);
+ event SourceGovernorUpdated(bytes32 indexed sourceGovernor);
+ event MessageReceived(bytes32 indexed sourceMessageSender, bytes data, bytes32 deliveryHash, uint256 sourceChain);
// L2 Wormhole Relayer address that receives the message across the bridge from the source L1 network
address public immutable wormholeRelayer;
// Source governor chain Id
uint16 public immutable sourceGovernorChainId;
// Source governor address on L1 that is authorized to propagate the transaction execution across the bridge
+ bytes32 public sourceGovernor;
+ // Source governor address on L1 that is authorized to propagate the transaction execution across the bridge
mapping(bytes32 => bool) public mapDeliveryHashes;
/// @dev WormholeMessenger constructor.
/// @param _wormholeRelayer L2 Wormhole Relayer address.
/// @param _sourceGovernor Source governor address (ETH).
/// @param _sourceGovernorChainId Source governor wormhole format chain Id.
- constructor(address _wormholeRelayer, address _sourceGovernor, uint16 _sourceGovernorChainId) {
+ constructor(address _wormholeRelayer, bytes32 _sourceGovernor, uint16 _sourceGovernorChainId) {
// Check for zero addresses
- if (_wormholeRelayer == address(0) || _sourceGovernor == address(0)) {
+ if (_wormholeRelayer == address(0)) {
revert ZeroAddress();
}
// Check source governor chain Id
- if (_sourceGovernorChainId == 0) {
+ if (_sourceGovernor == 0 || _sourceGovernorChainId == 0) {
revert ZeroValue();
}
@@ -37,6 +40,26 @@ contract WormholeMessenger is BridgeMessenger {
sourceGovernorChainId = _sourceGovernorChainId;
}
+ /// @dev Changes the source governor address (original Timelock).
+ /// @notice The only way to change the source governor address is by the Timelock on L1 to request that change.
+ /// This triggers a self-contract transaction of BridgeMessenger that changes the source governor address.
+ /// @param newSourceGovernor New source governor address.
+ function changeSourceGovernor(bytes32 newSourceGovernor) external virtual {
+ // Check if the change is authorized by the previous governor itself
+ // This is possible only if all the checks in the message process function pass and the contract calls itself
+ if (msg.sender != address(this)) {
+ revert SelfCallOnly(msg.sender, address(this));
+ }
+
+ // Check for the zero address
+ if (newSourceGovernor == 0) {
+ revert ZeroValue();
+ }
+
+ sourceGovernor = newSourceGovernor;
+ emit SourceGovernorUpdated(newSourceGovernor);
+ }
+
/// @dev Processes a message received from L2 Wormhole Relayer contract.
/// @notice The sender must be the source governor address (Timelock).
/// @param data Bytes message sent from L2 Wormhole Relayer contract. The data must be encoded as a set of
@@ -66,10 +89,9 @@ contract WormholeMessenger is BridgeMessenger {
}
// Check for the source governor address
- address governor = sourceGovernor;
- address bridgeGovernor = address(uint160(uint256(sourceAddress)));
- if (bridgeGovernor != governor) {
- revert SourceGovernorOnly(bridgeGovernor, governor);
+ bytes32 governor = sourceGovernor;
+ if (governor != sourceAddress) {
+ revert SourceGovernorOnly32(sourceAddress, governor);
}
// Check the delivery hash uniqueness
diff --git a/contracts/interfaces/IBridgeErrors.sol b/contracts/interfaces/IBridgeErrors.sol
index 8a3d0e9..113430a 100644
--- a/contracts/interfaces/IBridgeErrors.sol
+++ b/contracts/interfaces/IBridgeErrors.sol
@@ -29,6 +29,11 @@ interface IBridgeErrors {
/// @param sourceGovernor Required source governor address.
error SourceGovernorOnly(address sender, address sourceGovernor);
+ /// @dev Only on behalf of `sourceGovernor` the function is allowed to process the data.
+ /// @param sender Sender address.
+ /// @param sourceGovernor Required source governor address.
+ error SourceGovernorOnly32(bytes32 sender, bytes32 sourceGovernor);
+
/// @dev The message with a specified hash has already been delivered.
/// @param deliveryHash Delivery hash.
error AlreadyDelivered(bytes32 deliveryHash);
diff --git a/test/OptimismMessenger.js b/test/OptimismMessenger.js
new file mode 100644
index 0000000..8147cb7
--- /dev/null
+++ b/test/OptimismMessenger.js
@@ -0,0 +1,271 @@
+/*global describe, context, beforeEach, it*/
+
+const { expect } = require("chai");
+const { ethers } = require("hardhat");
+
+describe("OptimismMessenger", function () {
+ let cdmContractProxy;
+ let optimismMessenger;
+ let olas;
+ let signers;
+ let deployer;
+ const AddressZero = ethers.constants.AddressZero;
+
+ beforeEach(async function () {
+ signers = await ethers.getSigners();
+ deployer = signers[0];
+
+ // Deploy the mock of CDMContractProxy contract
+ const CDMContractProxy = await ethers.getContractFactory("MockL2Relayer");
+ cdmContractProxy = await CDMContractProxy.deploy(deployer.address, deployer.address);
+ await cdmContractProxy.deployed();
+
+ // The deployer is the analogue of the Timelock on L1 and the FxRoot mock on L1 as well
+ const OptimismMessenger = await ethers.getContractFactory("OptimismMessenger");
+ optimismMessenger = await OptimismMessenger.deploy(cdmContractProxy.address, deployer.address);
+ await optimismMessenger.deployed();
+
+ // Change the OptimismMessenger contract address in the CDMContractProxy
+ await cdmContractProxy.changeBridgeMessenger(optimismMessenger.address);
+
+ // OLAS represents a contract deployed on L2
+ const OLAS = await ethers.getContractFactory("OLAS");
+ olas = await OLAS.deploy();
+ await olas.deployed();
+ });
+
+ context("Initialization", async function () {
+ it("Deploying with zero addresses", async function () {
+ const OptimismMessenger = await ethers.getContractFactory("OptimismMessenger");
+ await expect(
+ OptimismMessenger.deploy(AddressZero, AddressZero)
+ ).to.be.revertedWithCustomError(optimismMessenger, "ZeroAddress");
+
+ await expect(
+ OptimismMessenger.deploy(signers[1].address, AddressZero)
+ ).to.be.revertedWithCustomError(optimismMessenger, "ZeroAddress");
+ });
+ });
+
+ context("Process message from source", async function () {
+ it("Should fail when trying to call from incorrect contract addresses", async function () {
+ await expect(
+ optimismMessenger.connect(deployer).processMessageFromSource("0x")
+ ).to.be.revertedWithCustomError(optimismMessenger, "TargetRelayerOnly");
+
+ // Simulate incorrect sourceGovernor address
+ await cdmContractProxy.changeSourceGovernor(AddressZero);
+ await expect(
+ cdmContractProxy.processMessageFromSource("0x")
+ ).to.be.revertedWithCustomError(optimismMessenger, "SourceGovernorOnly");
+ });
+
+ it("Should fail when trying to process message with the incorrect minimal payload length", async function () {
+ await expect(
+ cdmContractProxy.processMessageFromSource("0x")
+ ).to.be.revertedWithCustomError(optimismMessenger, "IncorrectDataLength");
+ });
+
+ it("Should fail when trying to change the source governor from any other contract / EOA", async function () {
+ await expect(
+ optimismMessenger.connect(deployer).changeSourceGovernor(signers[1].address)
+ ).to.be.revertedWithCustomError(optimismMessenger, "SelfCallOnly");
+ });
+
+ it("Should fail when trying to call with the zero address", async function () {
+ const target = AddressZero;
+ const value = 0;
+ const payload = "";
+ const data = ethers.utils.solidityPack(
+ ["address", "uint96", "uint32"],
+ [target, value, payload.length]
+ );
+
+ await expect(
+ cdmContractProxy.processMessageFromSource(data)
+ ).to.be.revertedWithCustomError(optimismMessenger, "ZeroAddress");
+ });
+
+ it("Should fail when trying to call with the incorrectly provided payload", async function () {
+ const target = olas.address;
+ const value = 0;
+ const rawPayload = "0x" + "abcd".repeat(10);
+ const payload = ethers.utils.arrayify(rawPayload);
+ const data = ethers.utils.solidityPack(
+ ["address", "uint96", "uint32", "bytes"],
+ [target, value, payload.length, payload]
+ );
+
+ await expect(
+ cdmContractProxy.processMessageFromSource(data)
+ ).to.be.revertedWithCustomError(optimismMessenger, "TargetExecFailed");
+ });
+
+ it("Unpack the data and call one specified target on the OLAS contract", async function () {
+ // Minter of OLAS must be the optimismMessenger contract
+ await olas.connect(deployer).changeMinter(optimismMessenger.address);
+
+ // OLAS contract across the bridge must mint 100 OLAS for the deployer
+ const amountToMint = 100;
+ const rawPayload = olas.interface.encodeFunctionData("mint", [deployer.address, amountToMint]);
+
+ // Pack the data into one contiguous buffer
+ const target = olas.address;
+ const value = 0;
+ const payload = ethers.utils.arrayify(rawPayload);
+ const data = ethers.utils.solidityPack(
+ ["address", "uint96", "uint32", "bytes"],
+ [target, value, payload.length, payload]
+ );
+
+ // Execute the unpacked transaction
+ await cdmContractProxy.processMessageFromSource(data);
+
+ // Check that OLAS tokens were minted to the deployer
+ const balance = Number(await olas.balanceOf(deployer.address));
+ expect(balance).to.equal(amountToMint);
+ });
+
+ it("Unpack the data and call two specified targets on the OLAS contract", async function () {
+ // Minter of OLAS must be the optimismMessenger contract
+ await olas.connect(deployer).changeMinter(optimismMessenger.address);
+ // Change OLAS owner to the optimismMessenger contract
+ await olas.connect(deployer).changeOwner(optimismMessenger.address);
+
+ // FxGivernorTunnel changes the minter to self as being the owner, then mint 100 OLAS for the deployer
+ const amountToMint = 100;
+ const payloads = [olas.interface.encodeFunctionData("changeMinter", [optimismMessenger.address]),
+ olas.interface.encodeFunctionData("mint", [deployer.address, amountToMint])];
+
+ // Pack the data into one contiguous buffer
+ const targets = [olas.address, olas.address];
+ const values = [0, 0];
+ let data = "0x";
+ for (let i = 0; i < targets.length; i++) {
+ const payload = ethers.utils.arrayify(payloads[i]);
+ const encoded = ethers.utils.solidityPack(
+ ["address", "uint96", "uint32", "bytes"],
+ [targets[i], values[i], payload.length, payload]
+ );
+ data += encoded.slice(2);
+ }
+
+ // Execute the unpacked transaction
+ await cdmContractProxy.processMessageFromSource(data);
+
+ // Check that OLAS tokens were minted to the deployer
+ const balance = Number(await olas.balanceOf(deployer.address));
+ expect(balance).to.equal(amountToMint);
+ });
+
+ it("Change the source governor", async function () {
+ const rawPayload = optimismMessenger.interface.encodeFunctionData("changeSourceGovernor", [signers[1].address]);
+
+ // Pack the data into one contiguous buffer
+ const target = optimismMessenger.address;
+ const value = 0;
+ const payload = ethers.utils.arrayify(rawPayload);
+ const data = ethers.utils.solidityPack(
+ ["address", "uint96", "uint32", "bytes"],
+ [target, value, payload.length, payload]
+ );
+
+ // Execute the unpacked transaction
+ await cdmContractProxy.processMessageFromSource(data);
+
+ // Check that the new source governor is signers[1].address
+ const sourceGovernor = await optimismMessenger.sourceGovernor();
+ expect(sourceGovernor).to.equal(signers[1].address);
+ });
+
+ it("Should fail when trying to change the source governor to the zero address", async function () {
+ const rawPayload = optimismMessenger.interface.encodeFunctionData("changeSourceGovernor", [AddressZero]);
+
+ // Pack the data into one contiguous buffer
+ const target = optimismMessenger.address;
+ const value = 0;
+ const payload = ethers.utils.arrayify(rawPayload);
+ const data = ethers.utils.solidityPack(
+ ["address", "uint96", "uint32", "bytes"],
+ [target, value, payload.length, payload]
+ );
+
+ // Execute the unpacked transaction
+ await expect(
+ cdmContractProxy.processMessageFromSource(data)
+ ).to.be.revertedWithCustomError(optimismMessenger, "TargetExecFailed");
+ });
+
+ it("Unpack the data and call one specified target to send funds", async function () {
+ const amount = ethers.utils.parseEther("1");
+ // Pack the data into one contiguous buffer
+ const target = deployer.address;
+ const value = amount;
+ const payloadLength = 0;
+ const data = ethers.utils.solidityPack(
+ ["address", "uint96", "uint32"],
+ [target, value, payloadLength]
+ );
+
+ // Try to execute the unpacked transaction without the contract having enough balance
+ await expect(
+ cdmContractProxy.processMessageFromSource(data)
+ ).to.be.revertedWithCustomError(optimismMessenger, "InsufficientBalance");
+
+ // Send funds to the contract
+ await deployer.sendTransaction({to: optimismMessenger.address, value: amount});
+
+ // Now the funds can be transferred
+ const balanceBefore = await ethers.provider.getBalance(deployer.address);
+ const tx = await cdmContractProxy.processMessageFromSource(data);
+ const receipt = await tx.wait();
+ const gasCost = ethers.BigNumber.from(receipt.gasUsed).mul(ethers.BigNumber.from(tx.gasPrice));
+ const balanceAfter = await ethers.provider.getBalance(deployer.address);
+ const balanceDiff = balanceAfter.sub(balanceBefore).add(gasCost);
+ expect(balanceDiff).to.equal(amount);
+ });
+
+ it("Unpack the data and call one specified target to send funds and to mint OLAS", async function () {
+ const amount = ethers.utils.parseEther("1");
+ const amountToMint = 100;
+ // Minter of OLAS must be the optimismMessenger contract
+ await olas.connect(deployer).changeMinter(optimismMessenger.address);
+
+ // Pack the first part of data with the zero payload
+ let target = deployer.address;
+ let value = amount;
+ const payloadLength = 0;
+ let data = ethers.utils.solidityPack(
+ ["address", "uint96", "uint32"],
+ [target, value, payloadLength]
+ );
+
+ // OLAS contract across the bridge must mint 100 OLAS for the deployer
+ const rawPayload = olas.interface.encodeFunctionData("mint", [deployer.address, amountToMint]);
+ // Pack the second part of data
+ target = olas.address;
+ value = 0;
+ const payload = ethers.utils.arrayify(rawPayload);
+ data += ethers.utils.solidityPack(
+ ["address", "uint96", "uint32", "bytes"],
+ [target, value, payload.length, payload]
+ ).slice(2);
+
+ // Send funds to the contract
+ await deployer.sendTransaction({to: optimismMessenger.address, value: amount});
+
+ // Execute the function and check for the deployer balance
+ const balanceBefore = await ethers.provider.getBalance(deployer.address);
+ const tx = await cdmContractProxy.processMessageFromSource(data);
+ const receipt = await tx.wait();
+ const gasCost = ethers.BigNumber.from(receipt.gasUsed).mul(ethers.BigNumber.from(tx.gasPrice));
+ const balanceAfter = await ethers.provider.getBalance(deployer.address);
+ const balanceDiff = balanceAfter.sub(balanceBefore).add(gasCost);
+ expect(balanceDiff).to.equal(amount);
+
+ // Check that OLAS tokens were minted to the deployer
+ const olasBalance = Number(await olas.balanceOf(deployer.address));
+ expect(olasBalance).to.equal(amountToMint);
+ });
+ });
+});
diff --git a/test/WormholeMessenger.js b/test/WormholeMessenger.js
new file mode 100644
index 0000000..b488960
--- /dev/null
+++ b/test/WormholeMessenger.js
@@ -0,0 +1,291 @@
+/*global describe, context, beforeEach, it*/
+
+const { expect } = require("chai");
+const { ethers } = require("hardhat");
+
+describe("WormholeMessenger", function () {
+ let targetRelayer;
+ let wormholeMessenger;
+ let olas;
+ let signers;
+ let deployer;
+ const AddressZero = ethers.constants.AddressZero;
+ const Bytes32Zero = "0x" + "0".repeat(64);
+ const sourceChain = 2;
+ const addressPrefix = "0x" + "0".repeat(24);
+
+ beforeEach(async function () {
+ signers = await ethers.getSigners();
+ deployer = signers[0];
+
+ // Deploy the mock of TargetRelayer contract
+ const TargetRelayer = await ethers.getContractFactory("MockL2Relayer");
+ targetRelayer = await TargetRelayer.deploy(deployer.address, deployer.address);
+ await targetRelayer.deployed();
+
+ // The deployer is the analogue of the Timelock on L1 and the FxRoot mock on L1 as well
+ const WormholeMessenger = await ethers.getContractFactory("WormholeMessenger");
+ wormholeMessenger = await WormholeMessenger.deploy(targetRelayer.address,
+ addressPrefix + deployer.address.slice(2), sourceChain);
+ await wormholeMessenger.deployed();
+
+ // Change the WormholeMessenger contract address in the TargetRelayer
+ await targetRelayer.changeBridgeMessenger(wormholeMessenger.address);
+
+ // OLAS represents a contract deployed on L2
+ const OLAS = await ethers.getContractFactory("OLAS");
+ olas = await OLAS.deploy();
+ await olas.deployed();
+ });
+
+ context("Initialization", async function () {
+ it("Deploying with zero addresses", async function () {
+ const WormholeMessenger = await ethers.getContractFactory("WormholeMessenger");
+ await expect(
+ WormholeMessenger.deploy(AddressZero, Bytes32Zero, 0)
+ ).to.be.revertedWithCustomError(wormholeMessenger, "ZeroAddress");
+
+ await expect(
+ WormholeMessenger.deploy(signers[1].address, Bytes32Zero, 0)
+ ).to.be.revertedWithCustomError(wormholeMessenger, "ZeroValue");
+
+ await expect(
+ WormholeMessenger.deploy(signers[1].address, addressPrefix + signers[2].address.slice(2), 0)
+ ).to.be.revertedWithCustomError(wormholeMessenger, "ZeroValue");
+ });
+ });
+
+ context("Process message from source", async function () {
+ it("Should fail when trying to call from incorrect contract addresses and chain Id", async function () {
+ await expect(
+ wormholeMessenger.connect(deployer).receiveWormholeMessages("0x", [], Bytes32Zero, 0, Bytes32Zero)
+ ).to.be.revertedWithCustomError(wormholeMessenger, "TargetRelayerOnly");
+
+ // Simulate incorrect sourceGovernor address
+ await targetRelayer.changeSourceGovernor(AddressZero);
+ await expect(
+ targetRelayer.receiveWormholeMessages("0x")
+ ).to.be.revertedWithCustomError(wormholeMessenger, "SourceGovernorOnly32");
+
+ // Simulate incorrect source chain Id
+ await targetRelayer.changeSourceChain(0);
+ await expect(
+ targetRelayer.receiveWormholeMessages("0x")
+ ).to.be.revertedWithCustomError(wormholeMessenger, "WrongSourceChainId");
+ });
+
+ it("Should fail when trying to process message with the incorrect minimal payload length", async function () {
+ await expect(
+ targetRelayer.receiveWormholeMessages("0x")
+ ).to.be.revertedWithCustomError(wormholeMessenger, "IncorrectDataLength");
+ });
+
+ it("Should fail when trying to change the source governor from any other contract / EOA", async function () {
+ await expect(
+ wormholeMessenger.connect(deployer).changeSourceGovernor(addressPrefix + signers[1].address.slice(2))
+ ).to.be.revertedWithCustomError(wormholeMessenger, "SelfCallOnly");
+ });
+
+ it("Should fail when trying to call with the zero address", async function () {
+ const target = AddressZero;
+ const value = 0;
+ const payload = "";
+ const data = ethers.utils.solidityPack(
+ ["address", "uint96", "uint32"],
+ [target, value, payload.length]
+ );
+
+ await expect(
+ targetRelayer.receiveWormholeMessages(data)
+ ).to.be.revertedWithCustomError(wormholeMessenger, "ZeroAddress");
+ });
+
+ it("Should fail when trying to call with the incorrectly provided payload", async function () {
+ const target = olas.address;
+ const value = 0;
+ const rawPayload = "0x" + "abcd".repeat(10);
+ const payload = ethers.utils.arrayify(rawPayload);
+ const data = ethers.utils.solidityPack(
+ ["address", "uint96", "uint32", "bytes"],
+ [target, value, payload.length, payload]
+ );
+
+ await expect(
+ targetRelayer.receiveWormholeMessages(data)
+ ).to.be.revertedWithCustomError(wormholeMessenger, "TargetExecFailed");
+ });
+
+ it("Unpack the data and call one specified target on the OLAS contract", async function () {
+ // Minter of OLAS must be the wormholeMessenger contract
+ await olas.connect(deployer).changeMinter(wormholeMessenger.address);
+
+ // OLAS contract across the bridge must mint 100 OLAS for the deployer
+ const amountToMint = 100;
+ const rawPayload = olas.interface.encodeFunctionData("mint", [deployer.address, amountToMint]);
+
+ // Pack the data into one contiguous buffer
+ const target = olas.address;
+ const value = 0;
+ const payload = ethers.utils.arrayify(rawPayload);
+ const data = ethers.utils.solidityPack(
+ ["address", "uint96", "uint32", "bytes"],
+ [target, value, payload.length, payload]
+ );
+
+ // Execute the unpacked transaction
+ await targetRelayer.receiveWormholeMessages(data);
+
+ // Check that OLAS tokens were minted to the deployer
+ const balance = Number(await olas.balanceOf(deployer.address));
+ expect(balance).to.equal(amountToMint);
+
+ // Try to unpack with the same delivery hash
+ await expect(
+ targetRelayer.receiveWormholeMessages(data)
+ ).to.be.revertedWithCustomError(wormholeMessenger, "AlreadyDelivered");
+ });
+
+ it("Unpack the data and call two specified targets on the OLAS contract", async function () {
+ // Minter of OLAS must be the wormholeMessenger contract
+ await olas.connect(deployer).changeMinter(wormholeMessenger.address);
+ // Change OLAS owner to the wormholeMessenger contract
+ await olas.connect(deployer).changeOwner(wormholeMessenger.address);
+
+ // FxGivernorTunnel changes the minter to self as being the owner, then mint 100 OLAS for the deployer
+ const amountToMint = 100;
+ const payloads = [olas.interface.encodeFunctionData("changeMinter", [wormholeMessenger.address]),
+ olas.interface.encodeFunctionData("mint", [deployer.address, amountToMint])];
+
+ // Pack the data into one contiguous buffer
+ const targets = [olas.address, olas.address];
+ const values = [0, 0];
+ let data = "0x";
+ for (let i = 0; i < targets.length; i++) {
+ const payload = ethers.utils.arrayify(payloads[i]);
+ const encoded = ethers.utils.solidityPack(
+ ["address", "uint96", "uint32", "bytes"],
+ [targets[i], values[i], payload.length, payload]
+ );
+ data += encoded.slice(2);
+ }
+
+ // Execute the unpacked transaction
+ await targetRelayer.receiveWormholeMessages(data);
+
+ // Check that OLAS tokens were minted to the deployer
+ const balance = Number(await olas.balanceOf(deployer.address));
+ expect(balance).to.equal(amountToMint);
+ });
+
+ it("Change the source governor", async function () {
+ const rawPayload = wormholeMessenger.interface.encodeFunctionData("changeSourceGovernor",
+ [addressPrefix + signers[1].address.slice(2)]);
+
+ // Pack the data into one contiguous buffer
+ const target = wormholeMessenger.address;
+ const value = 0;
+ const payload = ethers.utils.arrayify(rawPayload);
+ const data = ethers.utils.solidityPack(
+ ["address", "uint96", "uint32", "bytes"],
+ [target, value, payload.length, payload]
+ );
+
+ // Execute the unpacked transaction
+ await targetRelayer.receiveWormholeMessages(data);
+
+ // Check that the new source governor is signers[1].address
+ const sourceGovernor = await wormholeMessenger.sourceGovernor();
+ expect(sourceGovernor).to.equal((addressPrefix + signers[1].address.slice(2)).toLowerCase());
+ });
+
+ it("Should fail when trying to change the source governor to the zero address", async function () {
+ const rawPayload = wormholeMessenger.interface.encodeFunctionData("changeSourceGovernor", [Bytes32Zero]);
+
+ // Pack the data into one contiguous buffer
+ const target = wormholeMessenger.address;
+ const value = 0;
+ const payload = ethers.utils.arrayify(rawPayload);
+ const data = ethers.utils.solidityPack(
+ ["address", "uint96", "uint32", "bytes"],
+ [target, value, payload.length, payload]
+ );
+
+ // Execute the unpacked transaction
+ await expect(
+ targetRelayer.receiveWormholeMessages(data)
+ ).to.be.revertedWithCustomError(wormholeMessenger, "TargetExecFailed");
+ });
+
+ it("Unpack the data and call one specified target to send funds", async function () {
+ const amount = ethers.utils.parseEther("1");
+ // Pack the data into one contiguous buffer
+ const target = deployer.address;
+ const value = amount;
+ const payloadLength = 0;
+ const data = ethers.utils.solidityPack(
+ ["address", "uint96", "uint32"],
+ [target, value, payloadLength]
+ );
+
+ // Try to execute the unpacked transaction without the contract having enough balance
+ await expect(
+ targetRelayer.receiveWormholeMessages(data)
+ ).to.be.revertedWithCustomError(wormholeMessenger, "InsufficientBalance");
+
+ // Send funds to the contract
+ await deployer.sendTransaction({to: wormholeMessenger.address, value: amount});
+
+ // Now the funds can be transferred
+ const balanceBefore = await ethers.provider.getBalance(deployer.address);
+ const tx = await targetRelayer.receiveWormholeMessages(data);
+ const receipt = await tx.wait();
+ const gasCost = ethers.BigNumber.from(receipt.gasUsed).mul(ethers.BigNumber.from(tx.gasPrice));
+ const balanceAfter = await ethers.provider.getBalance(deployer.address);
+ const balanceDiff = balanceAfter.sub(balanceBefore).add(gasCost);
+ expect(balanceDiff).to.equal(amount);
+ });
+
+ it("Unpack the data and call one specified target to send funds and to mint OLAS", async function () {
+ const amount = ethers.utils.parseEther("1");
+ const amountToMint = 100;
+ // Minter of OLAS must be the wormholeMessenger contract
+ await olas.connect(deployer).changeMinter(wormholeMessenger.address);
+
+ // Pack the first part of data with the zero payload
+ let target = deployer.address;
+ let value = amount;
+ const payloadLength = 0;
+ let data = ethers.utils.solidityPack(
+ ["address", "uint96", "uint32"],
+ [target, value, payloadLength]
+ );
+
+ // OLAS contract across the bridge must mint 100 OLAS for the deployer
+ const rawPayload = olas.interface.encodeFunctionData("mint", [deployer.address, amountToMint]);
+ // Pack the second part of data
+ target = olas.address;
+ value = 0;
+ const payload = ethers.utils.arrayify(rawPayload);
+ data += ethers.utils.solidityPack(
+ ["address", "uint96", "uint32", "bytes"],
+ [target, value, payload.length, payload]
+ ).slice(2);
+
+ // Send funds to the contract
+ await deployer.sendTransaction({to: wormholeMessenger.address, value: amount});
+
+ // Execute the function and check for the deployer balance
+ const balanceBefore = await ethers.provider.getBalance(deployer.address);
+ const tx = await targetRelayer.receiveWormholeMessages(data);
+ const receipt = await tx.wait();
+ const gasCost = ethers.BigNumber.from(receipt.gasUsed).mul(ethers.BigNumber.from(tx.gasPrice));
+ const balanceAfter = await ethers.provider.getBalance(deployer.address);
+ const balanceDiff = balanceAfter.sub(balanceBefore).add(gasCost);
+ expect(balanceDiff).to.equal(amount);
+
+ // Check that OLAS tokens were minted to the deployer
+ const olasBalance = Number(await olas.balanceOf(deployer.address));
+ expect(olasBalance).to.equal(amountToMint);
+ });
+ });
+});