Skip to content

Commit

Permalink
Store hash of execution data
Browse files Browse the repository at this point in the history
Reduces storage gas cost, but requires caller to pass in full execution
data.

Fixes #35.
  • Loading branch information
Dominator008 committed Oct 9, 2023
1 parent 0575e02 commit d22798f
Show file tree
Hide file tree
Showing 11 changed files with 320 additions and 101 deletions.
29 changes: 20 additions & 9 deletions src/MultiBridgeMessageReceiver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ contract MultiBridgeMessageReceiver is IMultiBridgeMessageReceiver, ExecutorAwar
/// @notice count of bridge adapters that have delivered each message
mapping(bytes32 msgId => uint256 deliveryCount) public msgDeliveryCount;

/// @notice the data required for executing a message
mapping(bytes32 msgId => ExecutionData execData) public msgExecData;
/// @notice the hash of the data required for executing a message
mapping(bytes32 msgId => bytes32 execDataHash) public msgExecDataHash;

/// @notice whether a message has been sent to the governance timelock for execution
mapping(bytes32 msgId => bool scheduled) public isExecutionScheduled;
Expand Down Expand Up @@ -130,13 +130,15 @@ contract MultiBridgeMessageReceiver is IMultiBridgeMessageReceiver, ExecutorAwar
/// increment quorum
++msgDeliveryCount[msgId];

/// stores the execution data required
ExecutionData memory prevStored = msgExecData[msgId];
/// stores the hash of the execution data required
bytes32 prevStoredHash = msgExecDataHash[msgId];

/// stores the message if the amb is the first one delivering the message
if (prevStored.target == address(0)) {
msgExecData[msgId] = ExecutionData(
_message.target, _message.callData, _message.nativeValue, _message.nonce, _message.expiration
if (prevStoredHash == bytes32(0)) {
msgExecDataHash[msgId] = keccak256(
abi.encodePacked(
_message.target, _message.callData, _message.nativeValue, _message.nonce, _message.expiration
)
);
}

Expand All @@ -145,8 +147,17 @@ contract MultiBridgeMessageReceiver is IMultiBridgeMessageReceiver, ExecutorAwar
}

/// @inheritdoc IMultiBridgeMessageReceiver
function scheduleMessageExecution(bytes32 _msgId) external override {
ExecutionData memory _execData = msgExecData[_msgId];
function scheduleMessageExecution(bytes32 _msgId, ExecutionData calldata _execData) external override {
bytes32 execDataHash = msgExecDataHash[_msgId];
if (
keccak256(
abi.encodePacked(
_execData.target, _execData.callData, _execData.value, _execData.nonce, _execData.expiration
)
) != execDataHash
) {
revert Error.EXEC_DATA_HASH_MISMATCH();
}

/// @dev validates if msg execution is not beyond expiration
if (block.timestamp > _execData.expiration) {
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/IMultiBridgeMessageReceiver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ interface IMultiBridgeMessageReceiver {

/// @notice Sends a message, that has achieved quorum and has not yet expired, to the governance timelock for eventual execution.
/// @param _msgId is the unique identifier of the message
function scheduleMessageExecution(bytes32 _msgId) external;
function scheduleMessageExecution(bytes32 _msgId, ExecutionData calldata _execData) external;

/// @notice adds or removes bridge receiver adapters.
/// @param _receiverAdapters the list of receiver adapters to add or remove
Expand Down
3 changes: 3 additions & 0 deletions src/libraries/Error.sol
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ library Error {
/// @dev is thrown if refund address is zero (or) invalid
error INVALID_REFUND_ADDRESS();

/// @dev is thrown if execution data does not match the stored hash
error EXEC_DATA_HASH_MISMATCH();

/*/////////////////////////////////////////////////////////////////
ADAPTER ERRORS
////////////////////////////////////////////////////////////////*/
Expand Down
24 changes: 20 additions & 4 deletions test/integration-tests/GracePeriodExpiry.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import "test/contracts-mock/MockUniswapReceiver.sol";

import {MultiBridgeMessageSender} from "src/MultiBridgeMessageSender.sol";
import {MultiBridgeMessageReceiver} from "src/MultiBridgeMessageReceiver.sol";
import {IMultiBridgeMessageReceiver} from "src/interfaces/IMultiBridgeMessageReceiver.sol";
import {Error} from "src/libraries/Error.sol";
import {GovernanceTimelock} from "src/controllers/GovernanceTimelock.sol";

Expand Down Expand Up @@ -40,11 +41,17 @@ contract GracePeriodExpiryTest is Setup {
0.01 ether,
wormholeFee
);
MultiBridgeMessageSender(contractAddress[SRC_CHAIN_ID][bytes("MMA_SENDER")]).remoteCall{value: 2 ether}(

bytes memory callData = abi.encode(MockUniswapReceiver.setValue.selector, "");
uint256 nativeValue = 0;
uint256 expiration = block.timestamp + EXPIRATION_CONSTANT;
MultiBridgeMessageSender sender = MultiBridgeMessageSender(contractAddress[SRC_CHAIN_ID][bytes("MMA_SENDER")]);
uint256 nonce = sender.nonce() + 1;
sender.remoteCall{value: 2 ether}(
DST_CHAIN_ID,
address(target),
abi.encode(MockUniswapReceiver.setValue.selector, ""),
0,
callData,
nativeValue,
EXPIRATION_CONSTANT,
refundAddress,
fees,
Expand All @@ -63,7 +70,16 @@ contract GracePeriodExpiryTest is Setup {
vm.selectFork(fork[DST_CHAIN_ID]);
vm.recordLogs();
/// schedule the message for execution by moving it to governance timelock contract
MultiBridgeMessageReceiver(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")]).scheduleMessageExecution(msgId);
MultiBridgeMessageReceiver(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")]).scheduleMessageExecution(
msgId,
IMultiBridgeMessageReceiver.ExecutionData({
target: address(target),
callData: callData,
value: nativeValue,
nonce: nonce,
expiration: expiration
})
);
(uint256 txId, address finalTarget, uint256 value, bytes memory data, uint256 eta) =
_getExecParams(vm.getRecordedLogs());

Expand Down
24 changes: 20 additions & 4 deletions test/integration-tests/MultiMessageAggregation.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import "test/contracts-mock/MockUniswapReceiver.sol";

import {MultiBridgeMessageSender} from "src/MultiBridgeMessageSender.sol";
import {MultiBridgeMessageReceiver} from "src/MultiBridgeMessageReceiver.sol";
import {IMultiBridgeMessageReceiver} from "src/interfaces/IMultiBridgeMessageReceiver.sol";
import {Error} from "src/libraries/Error.sol";
import {GovernanceTimelock} from "src/controllers/GovernanceTimelock.sol";

Expand Down Expand Up @@ -40,11 +41,17 @@ contract MultiBridgeMessageAggregationTest is Setup {
0.01 ether,
wormholeFee
);
MultiBridgeMessageSender(contractAddress[SRC_CHAIN_ID][bytes("MMA_SENDER")]).remoteCall{value: 2 ether}(

bytes memory callData = abi.encode(MockUniswapReceiver.setValue.selector, "");
uint256 nativeValue = 0;
uint256 expiration = block.timestamp + EXPIRATION_CONSTANT;
MultiBridgeMessageSender sender = MultiBridgeMessageSender(contractAddress[SRC_CHAIN_ID][bytes("MMA_SENDER")]);
uint256 nonce = sender.nonce() + 1;
sender.remoteCall{value: 2 ether}(
DST_CHAIN_ID,
address(target),
abi.encode(MockUniswapReceiver.setValue.selector, ""),
0,
callData,
nativeValue,
EXPIRATION_CONSTANT,
refundAddress,
fees,
Expand All @@ -63,7 +70,16 @@ contract MultiBridgeMessageAggregationTest is Setup {
vm.selectFork(fork[DST_CHAIN_ID]);
vm.recordLogs();
/// schedule the message for execution by moving it to governance timelock contract
MultiBridgeMessageReceiver(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")]).scheduleMessageExecution(msgId);
MultiBridgeMessageReceiver(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")]).scheduleMessageExecution(
msgId,
IMultiBridgeMessageReceiver.ExecutionData({
target: address(target),
callData: callData,
value: nativeValue,
nonce: nonce,
expiration: expiration
})
);
(uint256 txId, address finalTarget, uint256 value, bytes memory data, uint256 eta) =
_getExecParams(vm.getRecordedLogs());

Expand Down
44 changes: 33 additions & 11 deletions test/integration-tests/RemoteAdapterAdd.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import "test/Setup.t.sol";

import {MultiBridgeMessageSender} from "src/MultiBridgeMessageSender.sol";
import {MultiBridgeMessageReceiver} from "src/MultiBridgeMessageReceiver.sol";
import {IMultiBridgeMessageReceiver} from "src/interfaces/IMultiBridgeMessageReceiver.sol";
import {Error} from "src/libraries/Error.sol";
import {GovernanceTimelock} from "src/controllers/GovernanceTimelock.sol";

Expand Down Expand Up @@ -51,7 +52,7 @@ contract RemoteAdapterAdd is Setup {
_adapterAdd(adaptersToAdd, operation);
}

function _adapterAdd(address[] memory adaptersToAdd, bool[] memory operation) internal {
function _adapterAdd(address[] memory adaptersToAdd, bool[] memory operation) private {
vm.selectFork(fork[ETHEREUM_CHAIN_ID]);
vm.startPrank(caller);

Expand All @@ -65,10 +66,28 @@ contract RemoteAdapterAdd is Setup {
0.01 ether,
wormholeFee
);
MultiBridgeMessageSender(contractAddress[SRC_CHAIN_ID][bytes("MMA_SENDER")]).remoteCall{value: 2 ether}(

_sendAndExecuteMessage(adaptersToAdd, operation, fees);

for (uint256 j; j < adaptersToAdd.length; ++j) {
bool isTrusted = MultiBridgeMessageReceiver(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")])
.isTrustedExecutor(adaptersToAdd[j]);
assert(isTrusted);
}
}

function _sendAndExecuteMessage(address[] memory adaptersToAdd, bool[] memory operation, uint256[] memory fees)
private
{
MultiBridgeMessageSender sender = MultiBridgeMessageSender(contractAddress[SRC_CHAIN_ID][bytes("MMA_SENDER")]);
uint256 nonce = sender.nonce() + 1;
bytes memory callData =
abi.encodeWithSelector(MultiBridgeMessageReceiver.updateReceiverAdapters.selector, adaptersToAdd, operation);
uint256 expiration = block.timestamp + EXPIRATION_CONSTANT;
sender.remoteCall{value: 2 ether}(
DST_CHAIN_ID,
address(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")]),
abi.encodeWithSelector(MultiBridgeMessageReceiver.updateReceiverAdapters.selector, adaptersToAdd, operation),
contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")],
callData,
0,
EXPIRATION_CONSTANT,
refundAddress,
Expand All @@ -88,7 +107,16 @@ contract RemoteAdapterAdd is Setup {
vm.selectFork(fork[DST_CHAIN_ID]);
vm.recordLogs();
/// schedule the message for execution by moving it to governance timelock contract
MultiBridgeMessageReceiver(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")]).scheduleMessageExecution(msgId);
MultiBridgeMessageReceiver(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")]).scheduleMessageExecution(
msgId,
IMultiBridgeMessageReceiver.ExecutionData({
target: contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")],
callData: callData,
value: 0,
nonce: nonce,
expiration: expiration
})
);
(uint256 txId, address finalTarget, uint256 value, bytes memory data, uint256 eta) =
_getExecParams(vm.getRecordedLogs());

Expand All @@ -97,11 +125,5 @@ contract RemoteAdapterAdd is Setup {
GovernanceTimelock(contractAddress[DST_CHAIN_ID][bytes("TIMELOCK")]).executeTransaction(
txId, finalTarget, value, data, eta
);

for (uint256 j; j < adaptersToAdd.length; ++j) {
bool isTrusted = MultiBridgeMessageReceiver(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")])
.isTrustedExecutor(adaptersToAdd[j]);
assert(isTrusted);
}
}
}
92 changes: 56 additions & 36 deletions test/integration-tests/RemoteAdapterRemove.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import "test/Setup.t.sol";

import {MultiBridgeMessageSender} from "src/MultiBridgeMessageSender.sol";
import {MultiBridgeMessageReceiver} from "src/MultiBridgeMessageReceiver.sol";
import {IMultiBridgeMessageReceiver} from "src/interfaces/IMultiBridgeMessageReceiver.sol";
import {Error} from "src/libraries/Error.sol";
import {GovernanceTimelock} from "src/controllers/GovernanceTimelock.sol";

Expand Down Expand Up @@ -70,15 +71,52 @@ contract RemoteAdapterRemove is Setup {
0.01 ether,
wormholeFee
);
MultiBridgeMessageSender(contractAddress[SRC_CHAIN_ID][bytes("MMA_SENDER")]).remoteCall{value: 2 ether}(

_sendAndExecuteMessage(newQuorum, adaptersToRemove, operation, fees);

/// @dev validates quorum post update
assertEq(MultiBridgeMessageReceiver(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")]).quorum(), newQuorum);

/// @dev validates adapters post update
for (uint256 j; j < adaptersToRemove.length; ++j) {
bool isTrusted = MultiBridgeMessageReceiver(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")])
.isTrustedExecutor(adaptersToRemove[j]);
assert(!isTrusted);
}
}

function _updateDummy() private {
address[] memory newDummyAdapter = new address[](1);
newDummyAdapter[0] = address(420);

/// true = add
/// false = remove
bool[] memory operation = new bool[](1);
operation[0] = true;

vm.startPrank(contractAddress[DST_CHAIN_ID]["TIMELOCK"]);
MultiBridgeMessageReceiver(contractAddress[DST_CHAIN_ID]["MMA_RECEIVER"]).updateReceiverAdapters(
newDummyAdapter, operation
);
vm.stopPrank();
}

function _sendAndExecuteMessage(
uint256 newQuorum,
address[] memory adaptersToRemove,
bool[] memory operation,
uint256[] memory fees
) private {
bytes memory callData = abi.encodeWithSelector(
MultiBridgeMessageReceiver.updateReceiverAdaptersAndQuorum.selector, adaptersToRemove, operation, newQuorum
);
uint256 expiration = block.timestamp + EXPIRATION_CONSTANT;
MultiBridgeMessageSender sender = MultiBridgeMessageSender(contractAddress[SRC_CHAIN_ID][bytes("MMA_SENDER")]);
uint256 nonce = sender.nonce() + 1;
sender.remoteCall{value: 2 ether}(
DST_CHAIN_ID,
address(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")]),
abi.encodeWithSelector(
MultiBridgeMessageReceiver.updateReceiverAdaptersAndQuorum.selector,
adaptersToRemove,
operation,
newQuorum
),
contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")],
callData,
0,
EXPIRATION_CONSTANT,
refundAddress,
Expand All @@ -98,7 +136,16 @@ contract RemoteAdapterRemove is Setup {
vm.selectFork(fork[DST_CHAIN_ID]);
vm.recordLogs();
/// schedule the message for execution by moving it to governance timelock contract
MultiBridgeMessageReceiver(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")]).scheduleMessageExecution(msgId);
MultiBridgeMessageReceiver(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")]).scheduleMessageExecution(
msgId,
IMultiBridgeMessageReceiver.ExecutionData({
target: contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")],
callData: callData,
value: 0,
nonce: nonce,
expiration: expiration
})
);
(uint256 txId, address finalTarget, uint256 value, bytes memory data, uint256 eta) =
_getExecParams(vm.getRecordedLogs());

Expand All @@ -107,32 +154,5 @@ contract RemoteAdapterRemove is Setup {
GovernanceTimelock(contractAddress[DST_CHAIN_ID][bytes("TIMELOCK")]).executeTransaction(
txId, finalTarget, value, data, eta
);

/// @dev validates quorum post update
uint256 currQuorum = MultiBridgeMessageReceiver(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")]).quorum();
assertEq(currQuorum, newQuorum);

/// @dev validates adapters post update
for (uint256 j; j < adaptersToRemove.length; ++j) {
bool isTrusted = MultiBridgeMessageReceiver(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")])
.isTrustedExecutor(adaptersToRemove[j]);
assert(!isTrusted);
}
}

function _updateDummy() internal {
address[] memory newDummyAdapter = new address[](1);
newDummyAdapter[0] = address(420);

/// true = add
/// false = remove
bool[] memory operation = new bool[](1);
operation[0] = true;

vm.startPrank(contractAddress[DST_CHAIN_ID]["TIMELOCK"]);
MultiBridgeMessageReceiver(contractAddress[DST_CHAIN_ID]["MMA_RECEIVER"]).updateReceiverAdapters(
newDummyAdapter, operation
);
vm.stopPrank();
}
}
Loading

0 comments on commit d22798f

Please sign in to comment.