diff --git a/src/testing/bridges/AMBBridgeTesting.sol b/src/testing/bridges/AMBBridgeTesting.sol index e1b28c6..0340e7d 100644 --- a/src/testing/bridges/AMBBridgeTesting.sol +++ b/src/testing/bridges/AMBBridgeTesting.sol @@ -3,9 +3,9 @@ pragma solidity >=0.8.0; import { Vm } from "forge-std/Vm.sol"; -import { Bridge } from "src/testing/Bridge.sol"; -import { Domain, DomainHelpers } from "src/testing/Domain.sol"; -import { RecordedLogs } from "src/testing/utils/RecordedLogs.sol"; +import { Bridge } from "../Bridge.sol"; +import { Domain, DomainHelpers } from "../Domain.sol"; +import { RecordedLogs } from "../utils/RecordedLogs.sol"; interface IAMB { function validatorContract() external view returns (address); @@ -71,7 +71,7 @@ library AMBBridgeTesting { return bridge; } - function relayMessagesToDestination(Bridge memory bridge, bool switchToDestinationFork) internal { + function relayMessagesToDestination(Bridge storage bridge, bool switchToDestinationFork) internal { bridge.destination.selectFork(); Vm.Log[] memory logs = bridge.ingestAndFilterLogs(true, USER_REQUEST_FOR_AFFIRMATION_TOPIC, USER_REQUEST_FOR_SIGNATURE_TOPIC, bridge.sourceCrossChainMessenger); @@ -82,7 +82,7 @@ library AMBBridgeTesting { } } - function relayMessagesToSource(Bridge memory bridge, bool switchToSourceFork) internal { + function relayMessagesToSource(Bridge storage bridge, bool switchToSourceFork) internal { bridge.source.selectFork(); Vm.Log[] memory logs = bridge.ingestAndFilterLogs(false, USER_REQUEST_FOR_AFFIRMATION_TOPIC, USER_REQUEST_FOR_SIGNATURE_TOPIC, bridge.destinationCrossChainMessenger); diff --git a/src/testing/bridges/ArbitrumBridgeTesting.sol b/src/testing/bridges/ArbitrumBridgeTesting.sol index e721b51..13adfa8 100644 --- a/src/testing/bridges/ArbitrumBridgeTesting.sol +++ b/src/testing/bridges/ArbitrumBridgeTesting.sol @@ -3,9 +3,9 @@ pragma solidity >=0.8.0; import { Vm } from "forge-std/Vm.sol"; -import { Bridge } from "src/testing/Bridge.sol"; -import { Domain, DomainHelpers } from "src/testing/Domain.sol"; -import { RecordedLogs } from "src/testing/utils/RecordedLogs.sol"; +import { Bridge } from "../Bridge.sol"; +import { Domain, DomainHelpers } from "../Domain.sol"; +import { RecordedLogs } from "../utils/RecordedLogs.sol"; interface InboxLike { function createRetryableTicket( @@ -122,7 +122,7 @@ library ArbitrumBridgeTesting { return bridge; } - function relayMessagesToDestination(Bridge memory bridge, bool switchToDestinationFork) internal { + function relayMessagesToDestination(Bridge storage bridge, bool switchToDestinationFork) internal { bridge.destination.selectFork(); Vm.Log[] memory logs = RecordedLogs.getLogs(); @@ -149,7 +149,7 @@ library ArbitrumBridgeTesting { } } - function relayMessagesToSource(Bridge memory bridge, bool switchToSourceFork) internal { + function relayMessagesToSource(Bridge storage bridge, bool switchToSourceFork) internal { bridge.source.selectFork(); Vm.Log[] memory logs = bridge.ingestAndFilterLogs(false, SEND_TO_L1_TOPIC, bridge.destinationCrossChainMessenger); diff --git a/src/testing/bridges/CCTPBridgeTesting.sol b/src/testing/bridges/CCTPBridgeTesting.sol index 229a2b2..5a90714 100644 --- a/src/testing/bridges/CCTPBridgeTesting.sol +++ b/src/testing/bridges/CCTPBridgeTesting.sol @@ -3,9 +3,9 @@ pragma solidity >=0.8.0; import { Vm } from "forge-std/Vm.sol"; -import { Bridge } from "src/testing/Bridge.sol"; -import { Domain, DomainHelpers } from "src/testing/Domain.sol"; -import { RecordedLogs } from "src/testing/utils/RecordedLogs.sol"; +import { Bridge } from "../Bridge.sol"; +import { Domain, DomainHelpers } from "../Domain.sol"; +import { RecordedLogs } from "../utils/RecordedLogs.sol"; interface IMessenger { function receiveMessage(bytes calldata message, bytes calldata attestation) external returns (bool success); @@ -71,7 +71,7 @@ library CCTPBridgeTesting { return bridge; } - function relayMessagesToDestination(Bridge memory bridge, bool switchToDestinationFork) internal { + function relayMessagesToDestination(Bridge storage bridge, bool switchToDestinationFork) internal { bridge.destination.selectFork(); Vm.Log[] memory logs = bridge.ingestAndFilterLogs(true, SENT_MESSAGE_TOPIC, bridge.sourceCrossChainMessenger); @@ -84,7 +84,7 @@ library CCTPBridgeTesting { } } - function relayMessagesToSource(Bridge memory bridge, bool switchToSourceFork) internal { + function relayMessagesToSource(Bridge storage bridge, bool switchToSourceFork) internal { bridge.source.selectFork(); Vm.Log[] memory logs = bridge.ingestAndFilterLogs(false, SENT_MESSAGE_TOPIC, bridge.destinationCrossChainMessenger); diff --git a/src/testing/bridges/OptimismBridgeTesting.sol b/src/testing/bridges/OptimismBridgeTesting.sol index 97626a5..7823e6f 100644 --- a/src/testing/bridges/OptimismBridgeTesting.sol +++ b/src/testing/bridges/OptimismBridgeTesting.sol @@ -3,9 +3,9 @@ pragma solidity >=0.8.0; import { Vm } from "forge-std/Vm.sol"; -import { Bridge } from "src/testing/Bridge.sol"; -import { Domain, DomainHelpers } from "src/testing/Domain.sol"; -import { RecordedLogs } from "src/testing/utils/RecordedLogs.sol"; +import { Bridge } from "../Bridge.sol"; +import { Domain, DomainHelpers } from "../Domain.sol"; +import { RecordedLogs } from "../utils/RecordedLogs.sol"; interface IMessenger { function sendMessage( @@ -78,7 +78,7 @@ library OptimismBridgeTesting { return bridge; } - function relayMessagesToDestination(Bridge memory bridge, bool switchToDestinationFork) internal { + function relayMessagesToDestination(Bridge storage bridge, bool switchToDestinationFork) internal { bridge.destination.selectFork(); address malias; @@ -100,7 +100,7 @@ library OptimismBridgeTesting { } } - function relayMessagesToSource(Bridge memory bridge, bool switchToSourceFork) internal { + function relayMessagesToSource(Bridge storage bridge, bool switchToSourceFork) internal { bridge.source.selectFork(); Vm.Log[] memory logs = bridge.ingestAndFilterLogs(false, SENT_MESSAGE_TOPIC, bridge.destinationCrossChainMessenger); diff --git a/src/testing/utils/RecordedLogs.sol b/src/testing/utils/RecordedLogs.sol index 4912a24..1ff477d 100644 --- a/src/testing/utils/RecordedLogs.sol +++ b/src/testing/utils/RecordedLogs.sol @@ -3,7 +3,7 @@ pragma solidity >=0.8.0; import { Vm } from "forge-std/Vm.sol"; -import { Bridge } from "src/testing/Bridge.sol"; +import { Bridge } from "../Bridge.sol"; library RecordedLogs { @@ -33,7 +33,7 @@ library RecordedLogs { return logs; } - function ingestAndFilterLogs(Bridge memory bridge, bool sourceToDestination, bytes32 topic0, bytes32 topic1, address emitter) internal returns (Vm.Log[] memory filteredLogs) { + function ingestAndFilterLogs(Bridge storage bridge, bool sourceToDestination, bytes32 topic0, bytes32 topic1, address emitter) internal returns (Vm.Log[] memory filteredLogs) { Vm.Log[] memory logs = RecordedLogs.getLogs(); uint256 lastIndex = sourceToDestination ? bridge.lastSourceLogIndex : bridge.lastDestinationLogIndex; uint256 pushedIndex = 0; @@ -53,7 +53,7 @@ library RecordedLogs { assembly { mstore(filteredLogs, pushedIndex) } } - function ingestAndFilterLogs(Bridge memory bridge, bool sourceToDestination, bytes32 topic, address emitter) internal returns (Vm.Log[] memory filteredLogs) { + function ingestAndFilterLogs(Bridge storage bridge, bool sourceToDestination, bytes32 topic, address emitter) internal returns (Vm.Log[] memory filteredLogs) { return ingestAndFilterLogs(bridge, sourceToDestination, topic, bytes32(0), emitter); } diff --git a/test/AMBReceiver.t.sol b/test/AMBReceiver.t.sol new file mode 100644 index 0000000..322ef6b --- /dev/null +++ b/test/AMBReceiver.t.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.8.0; + +import "forge-std/Test.sol"; + +import { TargetContractMock } from "test/mocks/TargetContractMock.sol"; + +import { AMBReceiver } from "src/receivers/AMBReceiver.sol"; + +contract AMBMock { + + bytes32 public messageSourceChainId; + address public messageSender; + + constructor(bytes32 _messageSourceChainId, address _messageSender) { + messageSourceChainId = _messageSourceChainId; + messageSender = _messageSender; + } + + function __setSourceChainId(bytes32 _messageSourceChainId) public { + messageSourceChainId = _messageSourceChainId; + } + + function __setSender(address _messageSender) public { + messageSender = _messageSender; + } + +} + +contract AMBReceiverTest is Test { + + AMBMock amb; + TargetContractMock target; + + AMBReceiver receiver; + + bytes32 sourceChainId = bytes32(uint256(1)); + address sourceAuthority = makeAddr("sourceAuthority"); + address randomAddress = makeAddr("randomAddress"); + + function setUp() public { + amb = new AMBMock(sourceChainId, sourceAuthority); + target = new TargetContractMock(); + + receiver = new AMBReceiver( + address(amb), + sourceChainId, + sourceAuthority, + address(target) + ); + } + + function test_constructor() public { + receiver = new AMBReceiver( + address(amb), + sourceChainId, + sourceAuthority, + address(target) + ); + + assertEq(receiver.amb(), address(amb)); + assertEq(receiver.sourceChainId(), sourceChainId); + assertEq(receiver.sourceAuthority(), sourceAuthority); + assertEq(receiver.target(), address(target)); + } + + function test_forward_invalidSender() public { + vm.prank(randomAddress); + vm.expectRevert("AMBReceiver/invalid-sender"); + TargetContractMock(address(receiver)).increment(); + } + + function test_forward_invalidSourceChainId() public { + amb.__setSourceChainId(bytes32(uint256(2))); + + vm.prank(address(amb)); + vm.expectRevert("AMBReceiver/invalid-sourceChainId"); + TargetContractMock(address(receiver)).increment(); + } + + function test_forward_invalidSourceAuthority() public { + amb.__setSender(randomAddress); + + vm.prank(address(amb)); + vm.expectRevert("AMBReceiver/invalid-sourceAuthority"); + TargetContractMock(address(receiver)).increment(); + } + + function test_forward_success() public { + assertEq(target.count(), 0); + vm.prank(address(amb)); + TargetContractMock(address(receiver)).increment(); + assertEq(target.count(), 1); + } + + function test_forward_revert() public { + vm.prank(address(amb)); + vm.expectRevert("TargetContract/error"); + TargetContractMock(address(receiver)).revertFunc(); + } + +} diff --git a/test/ArbitrumReceiver.t.sol b/test/ArbitrumReceiver.t.sol new file mode 100644 index 0000000..7ae6bf3 --- /dev/null +++ b/test/ArbitrumReceiver.t.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.8.0; + +import "forge-std/Test.sol"; + +import { TargetContractMock } from "test/mocks/TargetContractMock.sol"; + +import { ArbitrumReceiver } from "src/receivers/ArbitrumReceiver.sol"; + +contract ArbitrumReceiverTest is Test { + + TargetContractMock target; + + ArbitrumReceiver receiver; + + address sourceAuthority = makeAddr("sourceAuthority"); + address sourceAuthorityWithOffset; + address randomAddress = makeAddr("randomAddress"); + + function setUp() public { + target = new TargetContractMock(); + + receiver = new ArbitrumReceiver( + sourceAuthority, + address(target) + ); + unchecked { + sourceAuthorityWithOffset = address(uint160(sourceAuthority) + uint160(0x1111000000000000000000000000000000001111)); + } + } + + function test_constructor() public { + receiver = new ArbitrumReceiver( + sourceAuthority, + address(target) + ); + + assertEq(receiver.l1Authority(), sourceAuthority); + assertEq(receiver.target(), address(target)); + } + + function test_forward_invalidL1Authority() public { + vm.prank(randomAddress); + vm.expectRevert("ArbitrumReceiver/invalid-l1Authority"); + TargetContractMock(address(receiver)).increment(); + } + + function test_forward_invalidL1AuthoritySourceAuthorityNoOffset() public { + vm.prank(sourceAuthority); + vm.expectRevert("ArbitrumReceiver/invalid-l1Authority"); + TargetContractMock(address(receiver)).increment(); + } + + function test_forward_success() public { + assertEq(target.count(), 0); + vm.prank(sourceAuthorityWithOffset); + TargetContractMock(address(receiver)).increment(); + assertEq(target.count(), 1); + } + + function test_forward_revert() public { + vm.prank(sourceAuthorityWithOffset); + vm.expectRevert("TargetContract/error"); + TargetContractMock(address(receiver)).revertFunc(); + } + +} diff --git a/test/CCTPReceiver.t.sol b/test/CCTPReceiver.t.sol new file mode 100644 index 0000000..7ad684f --- /dev/null +++ b/test/CCTPReceiver.t.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.8.0; + +import "forge-std/Test.sol"; + +import { TargetContractMock } from "test/mocks/TargetContractMock.sol"; + +import { CCTPReceiver } from "src/receivers/CCTPReceiver.sol"; + +contract CCTPReceiverTest is Test { + + TargetContractMock target; + + CCTPReceiver receiver; + + address destinationMessenger = makeAddr("destinationMessenger"); + uint32 sourceDomainId = 1; + bytes32 sourceAuthority = bytes32(uint256(uint160(makeAddr("sourceAuthority")))); + address randomAddress = makeAddr("randomAddress"); + + function setUp() public { + target = new TargetContractMock(); + + receiver = new CCTPReceiver( + destinationMessenger, + sourceDomainId, + sourceAuthority, + address(target) + ); + } + + function test_constructor() public { + receiver = new CCTPReceiver( + destinationMessenger, + sourceDomainId, + sourceAuthority, + address(target) + ); + + assertEq(receiver.destinationMessenger(), destinationMessenger); + assertEq(receiver.sourceDomainId(), sourceDomainId); + assertEq(receiver.sourceAuthority(), sourceAuthority); + assertEq(receiver.target(), address(target)); + } + + function test_handleReceiveMessage_invalidSender() public { + vm.prank(randomAddress); + vm.expectRevert("CCTPReceiver/invalid-sender"); + receiver.handleReceiveMessage( + sourceDomainId, + sourceAuthority, + abi.encodeCall(TargetContractMock.increment, ()) + ); + } + + function test_handleReceiveMessage_invalidSourceChainId() public { + vm.prank(destinationMessenger); + vm.expectRevert("CCTPReceiver/invalid-sourceDomain"); + receiver.handleReceiveMessage( + 2, + sourceAuthority, + abi.encodeCall(TargetContractMock.increment, ()) + ); + } + + function test_handleReceiveMessage_invalidSourceAuthority() public { + vm.prank(destinationMessenger); + vm.expectRevert("CCTPReceiver/invalid-sourceAuthority"); + receiver.handleReceiveMessage( + sourceDomainId, + bytes32(uint256(uint160(randomAddress))), + abi.encodeCall(TargetContractMock.increment, ()) + ); + } + + function test_handleReceiveMessage_success() public { + assertEq(target.count(), 0); + vm.prank(destinationMessenger); + bool result = receiver.handleReceiveMessage( + sourceDomainId, + sourceAuthority, + abi.encodeCall(TargetContractMock.increment, ()) + ); + assertEq(result, true); + assertEq(target.count(), 1); + } + + function test_handleReceiveMessage_revert() public { + vm.prank(destinationMessenger); + vm.expectRevert("TargetContract/error"); + receiver.handleReceiveMessage( + sourceDomainId, + sourceAuthority, + abi.encodeCall(TargetContractMock.revertFunc, ()) + ); + } + +} diff --git a/test/IntegrationBase.t.sol b/test/IntegrationBase.t.sol index 2f40cc9..b81f644 100644 --- a/test/IntegrationBase.t.sol +++ b/test/IntegrationBase.t.sol @@ -105,6 +105,25 @@ abstract contract IntegrationBaseTest is Test { assertEq(moSource.length(), 2); assertEq(moSource.messages(0), 3); assertEq(moSource.messages(1), 4); + + // Do one more message both ways to ensure subsequent calls don't repeat already sent messages + vm.startPrank(sourceAuthority); + queueSourceToDestination(abi.encodeCall(MessageOrdering.push, (5))); + vm.stopPrank(); + + relaySourceToDestination(); + + assertEq(moDestination.length(), 3); + assertEq(moDestination.messages(2), 5); + + vm.startPrank(destinationAuthority); + queueDestinationToSource(abi.encodeCall(MessageOrdering.push, (6))); + vm.stopPrank(); + + relayDestinationToSource(); + + assertEq(moSource.length(), 3); + assertEq(moSource.messages(2), 6); } function initSourceReceiver() internal virtual returns (address); diff --git a/test/OptimismReceiver.t.sol b/test/OptimismReceiver.t.sol new file mode 100644 index 0000000..85d18a1 --- /dev/null +++ b/test/OptimismReceiver.t.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.8.0; + +import "forge-std/Test.sol"; + +import { TargetContractMock } from "test/mocks/TargetContractMock.sol"; + +import { OptimismReceiver } from "src/receivers/OptimismReceiver.sol"; + +contract OptimismMessengerMock { + + address public xDomainMessageSender; + + function __setSender(address _xDomainMessageSender) public { + xDomainMessageSender = _xDomainMessageSender; + } + +} + +contract OptimismReceiverTest is Test { + + OptimismMessengerMock l2CrossDomain; + TargetContractMock target; + + OptimismReceiver receiver; + + address l2CrossDomainAddr = 0x4200000000000000000000000000000000000007; + + address sourceAuthority = makeAddr("sourceAuthority"); + address randomAddress = makeAddr("randomAddress"); + + function setUp() public { + // Set the code at the particular address + l2CrossDomain = new OptimismMessengerMock(); + vm.etch(l2CrossDomainAddr, address(l2CrossDomain).code); + l2CrossDomain = OptimismMessengerMock(l2CrossDomainAddr); + l2CrossDomain.__setSender(sourceAuthority); + + target = new TargetContractMock(); + + receiver = new OptimismReceiver( + sourceAuthority, + address(target) + ); + } + + function test_constructor() public { + receiver = new OptimismReceiver( + sourceAuthority, + address(target) + ); + + assertEq(receiver.l1Authority(), sourceAuthority); + assertEq(receiver.target(), address(target)); + } + + function test_forward_invalidSender() public { + vm.prank(randomAddress); + vm.expectRevert("OptimismReceiver/invalid-sender"); + TargetContractMock(address(receiver)).increment(); + } + + function test_forward_invalidL1Authority() public { + l2CrossDomain.__setSender(randomAddress); + + vm.prank(address(l2CrossDomain)); + vm.expectRevert("OptimismReceiver/invalid-l1Authority"); + TargetContractMock(address(receiver)).increment(); + } + + function test_forward_success() public { + assertEq(target.count(), 0); + vm.prank(address(l2CrossDomain)); + TargetContractMock(address(receiver)).increment(); + assertEq(target.count(), 1); + } + + function test_forward_revert() public { + vm.prank(address(l2CrossDomain)); + vm.expectRevert("TargetContract/error"); + TargetContractMock(address(receiver)).revertFunc(); + } + +} diff --git a/test/mocks/TargetContractMock.sol b/test/mocks/TargetContractMock.sol new file mode 100644 index 0000000..47c3052 --- /dev/null +++ b/test/mocks/TargetContractMock.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.8.0; + +contract TargetContractMock { + + uint256 public count; + + function increment() external { + count++; + } + + function revertFunc() external pure { + revert("TargetContract/error"); + } + +}