Skip to content

Commit

Permalink
example: add CrossChainPingPong
Browse files Browse the repository at this point in the history
  • Loading branch information
jakim929 committed Sep 16, 2024
1 parent 09b84fa commit dad3015
Show file tree
Hide file tree
Showing 3 changed files with 273 additions and 1 deletion.
2 changes: 1 addition & 1 deletion contracts/lib/optimism
Submodule optimism updated 37 files
+1 −0 op-chain-ops/interopgen/deploy.go
+1 −0 op-chain-ops/interopgen/deployers/implementations.go
+9 −0 op-chain-ops/script/deterministic.go
+7 −0 op-chain-ops/script/prank.go
+14 −0 op-chain-ops/script/script.go
+6 −4 op-chain-ops/script/script_test.go
+39 −38 op-e2e/actions/proofs/channel_timeout_test.go
+30 −29 op-e2e/actions/proofs/garbage_channel_test.go
+17 −17 op-e2e/actions/proofs/helpers/env.go
+1 −1 op-e2e/actions/proofs/helpers/fixture.go
+1 −1 op-e2e/actions/proofs/helpers/matrix.go
+22 −21 op-e2e/actions/proofs/sequence_window_expiry_test.go
+21 −20 op-e2e/actions/proofs/simple_program_test.go
+31 −0 op-e2e/e2eutils/setuputils/utils.go
+85 −0 op-e2e/interop/interop_test.go
+616 −0 op-e2e/interop/supersystem.go
+2 −1 op-e2e/sequencer_failover_setup.go
+4 −23 op-e2e/setup.go
+7 −3 op-e2e/tx_helper.go
+20 −1 op-service/sources/supervisor_client.go
+7 −3 op-supervisor/supervisor/backend/backend.go
+3 −0 op-supervisor/supervisor/backend/source/chain_processor.go
+9 −1 op-supervisor/supervisor/service.go
+51 −24 packages/contracts-bedrock/scripts/DeployImplementations.s.sol
+1 −0 packages/contracts-bedrock/scripts/DeployOPChain.s.sol
+12 −0 packages/contracts-bedrock/scripts/libraries/DeployUtils.sol
+2 −2 packages/contracts-bedrock/semver-lock.json
+104 −79 packages/contracts-bedrock/snapshots/abi/OPStackManager.json
+104 −79 packages/contracts-bedrock/snapshots/abi/OPStackManagerInterop.json
+18 −4 packages/contracts-bedrock/snapshots/storageLayout/OPStackManager.json
+18 −4 packages/contracts-bedrock/snapshots/storageLayout/OPStackManagerInterop.json
+32 −29 packages/contracts-bedrock/src/L1/OPStackManager.sol
+3 −4 packages/contracts-bedrock/src/L1/OPStackManagerInterop.sol
+47 −3 packages/contracts-bedrock/test/DeployImplementations.t.sol
+9 −12 packages/contracts-bedrock/test/L1/OPStackManager.t.sol
+2 −2 packages/contracts-bedrock/test/Specs.t.sol
+4 −1 packages/contracts-bedrock/test/vendor/Initializable.t.sol
96 changes: 96 additions & 0 deletions contracts/src/CrossChainPingPong.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import {IL2ToL2CrossDomainMessenger} from "@contracts-bedrock/L2/interfaces/IL2ToL2CrossDomainMessenger.sol";
import {Predeploys} from "@contracts-bedrock/libraries/Predeploys.sol";

error CallerNotL2ToL2CrossDomainMessenger();

error InvalidCrossDomainSender();

struct PingPongBall {
uint256 rallyCount;
uint256 lastHitterChainId;
address lastHitterAddress;
}

contract CrossChainPingPong {
event BallSent(uint256 indexed toChainId, PingPongBall ball);

event BallReceived(uint256 indexed fromChainId, PingPongBall ball);

address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER;

mapping(uint256 => bool) internal allowedChainIds;

uint256 internal serverChainId;
bool internal serverAlreadyServed;

PingPongBall internal receivedBall;

constructor(uint256[] memory _allowedChainIds, uint256 _serverChainId) {
for (uint256 i = 0; i < _allowedChainIds.length; i++) {
allowedChainIds[_allowedChainIds[i]] = true;
}

require(allowedChainIds[_serverChainId], "Invalid first server chain ID");

serverChainId = _serverChainId;
}

function serveBall(uint256 _toChainId) external {
require(serverChainId == block.chainid, "Cannot serve ball from this chain");
require(serverAlreadyServed == false, "Ball already served");
require(_isValidDestinationChain(_toChainId), "Invalid destination chain ID");

serverAlreadyServed = true;

PingPongBall memory _newBall = PingPongBall(1, block.chainid, msg.sender);

_sendBallMessage(_newBall, _toChainId);

emit BallSent(_toChainId, _newBall);
}

function sendBall(uint256 _toChainId) public {
require(_isBallOnThisChain(), "Ball is not on this chain");
require(_isValidDestinationChain(_toChainId), "Invalid destination chain ID");

PingPongBall memory _newBall = PingPongBall(receivedBall.rallyCount + 1, block.chainid, msg.sender);

delete receivedBall;

_sendBallMessage(_newBall, _toChainId);

emit BallSent(_toChainId, _newBall);
}

function receiveBall(PingPongBall memory _ball) external {
if (msg.sender != MESSENGER) {
revert CallerNotL2ToL2CrossDomainMessenger();
}

if (IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSender() != address(this)) {
revert InvalidCrossDomainSender();
}

receivedBall.lastHitterAddress = _ball.lastHitterAddress;
receivedBall.lastHitterChainId = _ball.lastHitterChainId;
receivedBall.rallyCount = _ball.rallyCount;

emit BallReceived(IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSource(), _ball);
}

function _sendBallMessage(PingPongBall memory _ball, uint256 _toChainId) internal {
bytes memory _message = abi.encodeCall(this.receiveBall, (_ball));
IL2ToL2CrossDomainMessenger(MESSENGER).sendMessage(_toChainId, address(this), _message);
}

function _isBallOnThisChain() internal view returns (bool) {
return receivedBall.lastHitterAddress != address(0);
}

function _isValidDestinationChain(uint256 _toChainId) internal view returns (bool) {
return allowedChainIds[_toChainId] && _toChainId != block.chainid;
}
}
176 changes: 176 additions & 0 deletions contracts/test/CrossChainPingPong.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import {Test} from "forge-std/Test.sol";
import {
CrossChainPingPong,
PingPongBall,
CallerNotL2ToL2CrossDomainMessenger,
InvalidCrossDomainSender
} from "../src/CrossChainPingPong.sol";
import {IL2ToL2CrossDomainMessenger} from "@contracts-bedrock/L2/interfaces/IL2ToL2CrossDomainMessenger.sol";
import {Predeploys} from "@contracts-bedrock/libraries/Predeploys.sol";

contract CrossChainPingPongTest is Test {
address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER;

CrossChainPingPong public crossChainPingPong;

address bob;
address sally;

// Helper function to mock and expect a call
function _mockAndExpect(address _receiver, bytes memory _calldata, bytes memory _returned) internal {
vm.mockCall(_receiver, _calldata, _returned);
vm.expectCall(_receiver, _calldata);
}

function setUp() public {
// Setting up allowed chains and server chain for testing
uint256[] memory allowedChains = new uint256[](2);
allowedChains[0] = 901;
allowedChains[1] = 902;

crossChainPingPong = new CrossChainPingPong(allowedChains, 901);

bob = vm.addr(1);
sally = vm.addr(2);
}

// Test serving the ball
function testServeBall() public {
uint256 toChainId = 902;

vm.chainId(901);

// Expect the BallSent event
PingPongBall memory _expectedBall = PingPongBall(1, block.chainid, bob);
vm.expectEmit(true, true, true, true, address(crossChainPingPong));
emit CrossChainPingPong.BallSent(toChainId, _expectedBall);

// Mock cross-chain message send call
bytes memory _message = abi.encodeCall(crossChainPingPong.receiveBall, (_expectedBall));
_mockAndExpect(
MESSENGER,
abi.encodeWithSelector(
IL2ToL2CrossDomainMessenger.sendMessage.selector, toChainId, address(crossChainPingPong), _message
),
abi.encode("")
);

// Serve the ball
vm.prank(bob);
crossChainPingPong.serveBall(toChainId);

// Ensure serve can only happen once
vm.expectRevert("Ball already served");
crossChainPingPong.serveBall(toChainId);
}

// Test receiving the ball from a valid cross-chain message
function testReceiveBall() public {
uint256 fromChainId = 901;

// Set up the mock for cross-domain message sender validation
PingPongBall memory _ball = PingPongBall(1, fromChainId, address(this));
_mockAndExpect(
MESSENGER,
abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector),
abi.encode(address(crossChainPingPong))
);

// Set up cross-domain message source mock
_mockAndExpect(
MESSENGER,
abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSource.selector),
abi.encode(fromChainId)
);

// Expect the BallReceived event
vm.expectEmit(true, true, true, true, address(crossChainPingPong));
emit CrossChainPingPong.BallReceived(fromChainId, _ball);

// Call receiveBall as if from the messenger
vm.prank(MESSENGER);
crossChainPingPong.receiveBall(_ball);
}

// Test receiving then sending the ball
function testSendBall() public {
// 1. receive a ball from 901 to 902
uint256 receiveFromChainId = 901;

// Set up the mock for cross-domain message sender validation
PingPongBall memory _ball = PingPongBall(1, receiveFromChainId, sally);
_mockAndExpect(
MESSENGER,
abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector),
abi.encode(address(crossChainPingPong))
);

// Set up cross-domain message source mock
_mockAndExpect(
MESSENGER,
abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSource.selector),
abi.encode(receiveFromChainId)
);

// Expect the BallReceived event
vm.expectEmit(true, true, true, true, address(crossChainPingPong));
emit CrossChainPingPong.BallReceived(receiveFromChainId, _ball);

vm.prank(MESSENGER);
crossChainPingPong.receiveBall(_ball);

// 2. send a ball from 902 to 901

uint256 sendFromChainId = 902;
uint256 sendToChainId = 901;
vm.chainId(sendFromChainId);

// Set up the expected event and mock
PingPongBall memory _newBall = PingPongBall(2, sendFromChainId, bob);

bytes memory _message = abi.encodeCall(crossChainPingPong.receiveBall, (_newBall));
_mockAndExpect(
MESSENGER,
abi.encodeWithSelector(
IL2ToL2CrossDomainMessenger.sendMessage.selector, sendToChainId, address(crossChainPingPong), _message
),
abi.encode("")
);

vm.expectEmit(true, true, true, true, address(crossChainPingPong));
emit CrossChainPingPong.BallSent(sendToChainId, _newBall);

// Send the ball
vm.prank(bob);
crossChainPingPong.sendBall(sendToChainId);
}

// Test receiving ball with an invalid cross-chain message sender
function testReceiveBallInvalidSender() public {
PingPongBall memory _ball = PingPongBall(1, block.chainid, address(this));

// Expect revert due to invalid sender
vm.expectRevert(CallerNotL2ToL2CrossDomainMessenger.selector);
crossChainPingPong.receiveBall(_ball);
}

// Test receiving ball with an invalid cross-domain source
function testReceiveBallInvalidCrossDomainSender() public {
PingPongBall memory _ball = PingPongBall(1, block.chainid, address(this));

// Mock the cross-domain message sender to be invalid
_mockAndExpect(
MESSENGER,
abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector),
abi.encode(address(0xdeadbeef)) // Invalid address
);

// Expect revert due to invalid cross-domain sender
vm.expectRevert(InvalidCrossDomainSender.selector);
vm.prank(MESSENGER);
crossChainPingPong.receiveBall(_ball);
}
}

0 comments on commit dad3015

Please sign in to comment.