Skip to content

Commit f6af25f

Browse files
RahulMaganti47Rahul Maganti
and
Rahul Maganti
authored
Manager: end execution early on replay protection (#101)
Co-authored-by: Rahul Maganti <rahulmaganti@rahuls-mbp.mynetworksettings.com>
1 parent 0a8b5e8 commit f6af25f

File tree

4 files changed

+39
-18
lines changed

4 files changed

+39
-18
lines changed

src/Manager.sol

+13-4
Original file line numberDiff line numberDiff line change
@@ -365,15 +365,17 @@ abstract contract Manager is
365365
}
366366

367367
// @dev Mark a message as executed.
368-
// This function will revert if the message has already been executed.
369-
function _replayProtect(bytes32 digest) internal {
368+
// This function will retuns `true` if the message has already been executed.
369+
function _replayProtect(bytes32 digest) internal returns (bool) {
370370
// check if this message has already been executed
371371
if (isMessageExecuted(digest)) {
372-
revert MessageAlreadyExecuted(digest);
372+
return true;
373373
}
374374

375375
// mark this message as executed
376376
_getMessageAttestationsStorage()[digest].executed = true;
377+
378+
return false;
377379
}
378380

379381
/// @dev Called after a message has been sufficiently verified to execute the command in the message.
@@ -394,7 +396,14 @@ abstract contract Manager is
394396
revert MessageNotApproved(digest);
395397
}
396398

397-
_replayProtect(digest);
399+
bool msgAlreadyExecuted = _replayProtect(digest);
400+
if (msgAlreadyExecuted) {
401+
// end execution early to mitigate the possibility of race conditions from endpoints
402+
// attempting to deliver the same message when (threshold < number of endpoint messages)
403+
// notify client (off-chain process) so they don't attempt redundant msg delivery
404+
emit MessageAlreadyExecuted(message.sourceManager, digest);
405+
return;
406+
}
398407

399408
EndpointStructs.NativeTokenTransfer memory nativeTokenTransfer =
400409
EndpointStructs.parseNativeTokenTransfer(message.payload);

src/interfaces/IManager.sol

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import "../libraries/NormalizedAmount.sol";
66
interface IManager {
77
error DeliveryPaymentTooLow(uint256 requiredPayment, uint256 providedPayment);
88
error TransferAmountHasDust(uint256 amount, uint256 dust);
9-
error MessageAlreadyExecuted(bytes32 msgHash);
109
error MessageNotApproved(bytes32 msgHash);
1110
error InvalidTargetChain(uint16 targetChain, uint16 thisChain);
1211
error ZeroAmount();

src/interfaces/IManagerEvents.sol

+1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ interface IManagerEvents {
1414
event ThresholdChanged(uint8 oldThreshold, uint8 threshold);
1515
event EndpointAdded(address endpoint, uint8 threshold);
1616
event EndpointRemoved(address endpoint, uint8 threshold);
17+
event MessageAlreadyExecuted(bytes32 indexed sourceManager, bytes32 indexed msgHash);
1718
}

test/Manager.t.sol

+25-13
Original file line numberDiff line numberDiff line change
@@ -486,12 +486,15 @@ contract TestManager is Test, IManagerEvents, IRateLimiterEvents {
486486
assertEq(token.balanceOf(address(user_B)), 50 * 10 ** (decimals - 8));
487487

488488
// replay protection
489-
vm.expectRevert(
490-
abi.encodeWithSignature(
491-
"MessageAlreadyExecuted(bytes32)", EndpointStructs.managerMessageDigest(m)
492-
)
493-
);
489+
vm.recordLogs();
494490
e2.receiveMessage(message);
491+
Vm.Log[] memory entries = vm.getRecordedLogs();
492+
493+
assertEq(entries.length, 2);
494+
assertEq(entries[1].topics.length, 3);
495+
assertEq(entries[1].topics[0], keccak256("MessageAlreadyExecuted(bytes32,bytes32)"));
496+
assertEq(entries[1].topics[1], toWormholeFormat(address(manager)));
497+
assertEq(entries[1].topics[2], bytes32(keccak256(message)));
495498
}
496499

497500
// TODO:
@@ -1004,9 +1007,15 @@ contract TestManager is Test, IManagerEvents, IRateLimiterEvents {
10041007
assertEq(token.balanceOf(address(user_B)), 50 * 10 ** (decimals - 8));
10051008

10061009
// replay protection
1007-
bytes4 selector = bytes4(keccak256("MessageAlreadyExecuted(bytes32)"));
1008-
vm.expectRevert(abi.encodeWithSelector(selector, EndpointStructs.managerMessageDigest(m)));
1010+
vm.recordLogs();
10091011
e2.receiveMessage(message);
1012+
Vm.Log[] memory entries = vm.getRecordedLogs();
1013+
1014+
assertEq(entries.length, 2);
1015+
assertEq(entries[1].topics.length, 3);
1016+
assertEq(entries[1].topics[0], keccak256("MessageAlreadyExecuted(bytes32,bytes32)"));
1017+
assertEq(entries[1].topics[1], toWormholeFormat(address(manager)));
1018+
assertEq(entries[1].topics[2], bytes32(keccak256(message)));
10101019
}
10111020

10121021
// === upgradeability
@@ -1053,13 +1062,16 @@ contract TestManager is Test, IManagerEvents, IRateLimiterEvents {
10531062
endpoints = new IEndpointReceiver[](1);
10541063
endpoints[0] = IEndpointReceiver(address(manager));
10551064

1056-
// replay protection
1057-
vm.expectRevert(
1058-
abi.encodeWithSignature(
1059-
"MessageAlreadyExecuted(bytes32)", EndpointStructs.managerMessageDigest(m)
1060-
)
1061-
);
1065+
vm.recordLogs();
1066+
10621067
IEndpointReceiver(address(manager)).receiveMessage(message);
1068+
Vm.Log[] memory entries = vm.getRecordedLogs();
1069+
1070+
assertEq(entries.length, 1);
1071+
assertEq(entries[0].topics.length, 3);
1072+
assertEq(entries[0].topics[0], keccak256("MessageAlreadyExecuted(bytes32,bytes32)"));
1073+
assertEq(entries[0].topics[1], toWormholeFormat(address(manager)));
1074+
assertEq(entries[0].topics[2], bytes32(keccak256(message)));
10631075

10641076
_attestEndpointsHelper(
10651077
user_A, user_B, 1, NormalizedAmount.wrap(type(uint64).max), endpoints

0 commit comments

Comments
 (0)