Skip to content

Commit

Permalink
feat: SuperchainWETHWrapper contract
Browse files Browse the repository at this point in the history
  • Loading branch information
tremarkley committed Sep 23, 2024
1 parent dfdc930 commit 2177e9d
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 33 deletions.
22 changes: 22 additions & 0 deletions contracts/script/DeployL2PeripheryContracts.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import {Script, console} from "forge-std/Script.sol";
import {ProxyAdmin} from "@contracts-bedrock/universal/ProxyAdmin.sol";
import {Proxy} from "@contracts-bedrock/universal/Proxy.sol";
import {L2NativeSuperchainERC20} from "../src/L2NativeSuperchainERC20.sol";
import {SuperchainETHWrapper} from "../src/SuperchainETHWrapper.sol";

contract DeployL2PeripheryContracts is Script {
address _proxyAdminContract;
address _l2NativeSuperchainERC20ProxyContract;
address _superchainETHWrapperProxyContract;
address _l2NativeSuperchainERC20Contract;
address _superchainETHWrapperContract;

/// @notice Modifier that wraps a function in broadcasting.
modifier broadcast() {
Expand Down Expand Up @@ -38,15 +41,18 @@ contract DeployL2PeripheryContracts is Script {
function deployProxies() public {
deployProxyAdmin();
deployL2NativeSuperchainERC20Proxy();
deploySuperchainETHWrapperProxy();
}

/// @notice Deploy all of the implementations
function deployImplementations() public {
deployL2NativeSuperchainERC20();
deploySuperchainETHWrapper();
}

function initializeContracts() public {
initializeL2NativeSuperchainERC20();
initializeSuperchainETHWrapper();
}

/// @notice Deploy the ProxyAdmin
Expand All @@ -63,13 +69,29 @@ contract DeployL2PeripheryContracts is Script {
console.log("Deployed L2NativeSuperchainERC20 at address: ", _l2NativeSuperchainERC20ProxyContract);
}

function deploySuperchainETHWrapperProxy() public {
bytes32 salt = keccak256(bytes("SuperchainETHWrapperProxy"));
_superchainETHWrapperProxyContract = address(new Proxy{salt: salt}(_proxyAdminContract));
console.log("Deployed SuperchainETHWrapper at address: ", _superchainETHWrapperProxyContract);
}

function deployL2NativeSuperchainERC20() public {
bytes32 salt = keccak256(bytes("L2NativeSuperchainERC20"));
_l2NativeSuperchainERC20Contract = address(new L2NativeSuperchainERC20{salt: salt}());
}

function deploySuperchainETHWrapper() public {
bytes32 salt = keccak256(bytes("SuperchainETHWrapper"));
_superchainETHWrapperContract = address(new SuperchainETHWrapper{salt: salt}());
}

function initializeL2NativeSuperchainERC20() public {
ProxyAdmin proxyAdmin = ProxyAdmin(_proxyAdminContract);
proxyAdmin.upgrade({_proxy: payable(_l2NativeSuperchainERC20ProxyContract), _implementation: _l2NativeSuperchainERC20Contract});
}

function initializeSuperchainETHWrapper() public {
ProxyAdmin proxyAdmin = ProxyAdmin(_proxyAdminContract);
proxyAdmin.upgrade({_proxy: payable(_superchainETHWrapperProxyContract), _implementation: _superchainETHWrapperContract});
}
}
72 changes: 72 additions & 0 deletions contracts/src/SuperchainETHWrapper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import {Unauthorized} from "@contracts-bedrock/libraries/errors/CommonErrors.sol";
import {Predeploys} from "@contracts-bedrock/libraries/Predeploys.sol";
import {SafeCall} from "@contracts-bedrock//libraries/SafeCall.sol";
import {IL2ToL2CrossDomainMessenger} from "@contracts-bedrock/L2/interfaces/IL2ToL2CrossDomainMessenger.sol";
import {ISuperchainERC20Extensions} from "@contracts-bedrock/L2/interfaces/ISuperchainERC20.sol";
import {IWETH} from "@contracts-bedrock/universal/interfaces/IWETH.sol";

interface IL2ToL2CrossDomainMessengerMissing {
function successfulMessages(bytes32) external view returns (bool);
function messageNonce() external view returns (uint256);
}

/**
* @title SuperchainETHWrapper
* @notice This contract implements a wrapping/unwrapping mechanism for
* native ether using a combination of the SuperchainWETH contract
* and the L2ToL2CrossDomainMessenger.
* @dev This contract relies on the L2ToL2CrossDomainMessenger for cross-chain communication.
*/
contract SuperchainETHWrapper {
event LogReceived(address from, uint256 value);

// Fallback function to receive ETH
receive() external payable {
emit LogReceived(msg.sender, msg.value);
}

function unwrapAndCall(bytes32 relayERC20MsgHash, address dst, uint256 wad, bytes memory _calldata) external {
// Receive message from other chain.
IL2ToL2CrossDomainMessenger messenger = IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
if (msg.sender != address(messenger)) revert Unauthorized();
if (messenger.crossDomainMessageSender() != address(this)) revert Unauthorized();

if (
IL2ToL2CrossDomainMessengerMissing(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER).successfulMessages(
relayERC20MsgHash
) == false
) {
revert();
}

IWETH(Predeploys.SUPERCHAIN_WETH).withdraw(wad);
SafeCall.call(dst, wad, _calldata);
}

function sendETH(address dst, uint256 chainId, bytes memory _calldata) public payable {
IWETH(Predeploys.SUPERCHAIN_WETH).deposit{value: msg.value}();

bytes32 relayERC20MessageHash = keccak256(
abi.encode(
chainId,
block.chainid,
IL2ToL2CrossDomainMessengerMissing(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER).messageNonce(),
Predeploys.SUPERCHAIN_WETH,
Predeploys.SUPERCHAIN_WETH,
abi.encodeCall(
ISuperchainERC20Extensions(Predeploys.SUPERCHAIN_WETH).relayERC20,
(address(this), address(this), msg.value)
)
)
);
ISuperchainERC20Extensions(Predeploys.SUPERCHAIN_WETH).sendERC20(address(this), msg.value, chainId);
IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER).sendMessage({
_destination: chainId,
_target: address(this),
_message: abi.encodeCall(this.unwrapAndCall, (relayERC20MessageHash, dst, msg.value, _calldata))
});
}
}
25 changes: 16 additions & 9 deletions contracts/src/TicTacToe.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
pragma solidity ^0.8.13;

contract TicTacToe {
enum PlayerTurn { PlayerOne, PlayerTwo }
enum PlayerTurn {
PlayerOne,
PlayerTwo
}

enum GameState {
WaitingForPlayer,
Expand Down Expand Up @@ -30,7 +33,7 @@ contract TicTacToe {

uint256 numOfGames;
mapping(uint256 => Game) public games;

// Magic Square: https://mathworld.wolfram.com/MagicSquare.html
uint8[3][3] private MAGIC_SQUARE = [[8, 3, 4], [1, 5, 9], [6, 7, 2]];
uint8 private constant MAGIC_SUM_PLAYER_ONE = 15;
Expand Down Expand Up @@ -58,7 +61,7 @@ contract TicTacToe {
return numOfGames;
}

function joinGame(address player2, uint256 gameId) public validGame(gameId) returns (bool) {
function joinGame(address player2, uint256 gameId) public validGame(gameId) returns (bool) {
Game storage game = games[gameId];

require(game.state == GameState.WaitingForPlayer, "Game already has enough players");
Expand All @@ -74,7 +77,7 @@ contract TicTacToe {
function makeMove(address player, uint256 gameId, uint8 x, uint8 y) public validGame(gameId) returns (bool) {
Game storage game = games[gameId];
require(game.state == GameState.Playing, "Game hasn't started or has already been completed");

bool isPlayerOneTurn = game.currentTurn == PlayerTurn.PlayerOne;
require(isPlayerOneTurn ? player == game.player1 : player == game.player2, "Not a valid player in the game");
require(x >= 0 && x <= 2 || y >= 0 && y <= 2, "Move out of bounds");
Expand All @@ -101,25 +104,29 @@ contract TicTacToe {
return games[gameId];
}

function checkForWin(address player, uint256 gameId) public validGame(gameId) view returns (bool) {
function checkForWin(address player, uint256 gameId) public view validGame(gameId) returns (bool) {
Game storage game = games[gameId];
require(player == game.player1 || player == game.player2, "Not a valid player in the game");

uint8 magicSum = player == game.player1 ? MAGIC_SUM_PLAYER_ONE : MAGIC_SUM_PLAYER_TWO;

// row & col check
for (uint8 i = 0; i < 3; i++) {
uint8 rowSum = (game.board[i][0] * MAGIC_SQUARE[i][0]) + (game.board[i][1] * MAGIC_SQUARE[i][1]) + (game.board[i][2] * MAGIC_SQUARE[i][2]);
uint8 colSum = (game.board[0][i] * MAGIC_SQUARE[0][i]) + (game.board[1][i] * MAGIC_SQUARE[1][i]) + (game.board[2][i] * MAGIC_SQUARE[2][i]);
uint8 rowSum = (game.board[i][0] * MAGIC_SQUARE[i][0]) + (game.board[i][1] * MAGIC_SQUARE[i][1])
+ (game.board[i][2] * MAGIC_SQUARE[i][2]);
uint8 colSum = (game.board[0][i] * MAGIC_SQUARE[0][i]) + (game.board[1][i] * MAGIC_SQUARE[1][i])
+ (game.board[2][i] * MAGIC_SQUARE[2][i]);

if (rowSum == magicSum || colSum == magicSum) {
return true;
}
}

// diag check
uint8 leftToRightSum = (game.board[0][0] * MAGIC_SQUARE[0][0]) + (game.board[1][1] * MAGIC_SQUARE[1][1]) + (game.board[2][2] * MAGIC_SQUARE[2][2]);
uint8 rightToLeftSum = (game.board[0][2] * MAGIC_SQUARE[0][2]) + (game.board[1][1] * MAGIC_SQUARE[1][1]) + (game.board[2][0] * MAGIC_SQUARE[2][0]);
uint8 leftToRightSum = (game.board[0][0] * MAGIC_SQUARE[0][0]) + (game.board[1][1] * MAGIC_SQUARE[1][1])
+ (game.board[2][2] * MAGIC_SQUARE[2][2]);
uint8 rightToLeftSum = (game.board[0][2] * MAGIC_SQUARE[0][2]) + (game.board[1][1] * MAGIC_SQUARE[1][1])
+ (game.board[2][0] * MAGIC_SQUARE[2][0]);
if (leftToRightSum == magicSum || rightToLeftSum == magicSum) {
return true;
}
Expand Down
12 changes: 6 additions & 6 deletions contracts/test/TicTacToe.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ contract TicTacToeTest is Test {
TicTacToe.Game memory game = ticTacToe.getGame(gameId);
assertEq(gameId, game.id);
assertEq(game.player1, player1);
assertEq(uint(game.state), uint(TicTacToe.GameState.WaitingForPlayer));
assertEq(uint256(game.state), uint256(TicTacToe.GameState.WaitingForPlayer));
}

function test_JoinGameSuccessful() public {
Expand Down Expand Up @@ -56,7 +56,7 @@ contract TicTacToeTest is Test {
ticTacToe.makeMove(player2, gameId, 1, 1);
ticTacToe.makeMove(player1, gameId, 0, 2);
TicTacToe.Game memory game = ticTacToe.getGame(gameId);
assertEq(uint(game.state), uint(TicTacToe.GameState.PlayerOneWins));
assertEq(uint256(game.state), uint256(TicTacToe.GameState.PlayerOneWins));

// Game 2: col win by player 2
gameId = ticTacToe.createGame(player1);
Expand All @@ -68,8 +68,8 @@ contract TicTacToeTest is Test {
ticTacToe.makeMove(player1, gameId, 1, 0);
ticTacToe.makeMove(player2, gameId, 2, 1);
game = ticTacToe.getGame(gameId);
assertEq(uint(game.state), uint(TicTacToe.GameState.PlayerTwoWins));
assertEq(uint256(game.state), uint256(TicTacToe.GameState.PlayerTwoWins));

// Game 3: dial win by player 1
gameId = ticTacToe.createGame(player1);
ticTacToe.joinGame(player2, gameId);
Expand All @@ -79,7 +79,7 @@ contract TicTacToeTest is Test {
ticTacToe.makeMove(player2, gameId, 1, 2);
ticTacToe.makeMove(player1, gameId, 2, 2);
game = ticTacToe.getGame(gameId);
assertEq(uint(game.state), uint(TicTacToe.GameState.PlayerOneWins));
assertEq(uint256(game.state), uint256(TicTacToe.GameState.PlayerOneWins));
}

function test_PlayGameUntilDraw() public {
Expand All @@ -96,6 +96,6 @@ contract TicTacToeTest is Test {
ticTacToe.makeMove(player2, gameId, 2, 1);
ticTacToe.makeMove(player1, gameId, 2, 2);
TicTacToe.Game memory game = ticTacToe.getGame(gameId);
assertEq(uint(game.state), uint(TicTacToe.GameState.Draw));
assertEq(uint256(game.state), uint256(TicTacToe.GameState.Draw));
}
}
27 changes: 10 additions & 17 deletions interop/relayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"fmt"
"math/big"
"sync"

"github.com/ethereum-optimism/optimism/op-service/predeploys"
"github.com/ethereum-optimism/optimism/op-service/tasks"
Expand Down Expand Up @@ -57,31 +56,30 @@ func (r *L2ToL2MessageRelayer) Start(indexer *L2ToL2MessageIndexer, clients map[

for destinationChainID, client := range r.clients {
r.tasks.Go(func() error {
var mu sync.Mutex
sentMessageCh := make(chan *L2ToL2MessageStoreEntry)
unsubscribe, err := r.l2ToL2MessageIndexer.SubscribeSentMessageToDestination(destinationChainID, sentMessageCh)

if err != nil {
return fmt.Errorf("failed to subscribe to sent message events: %w", err)
}

transactor, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(int64(destinationChainID)))
if err != nil {
return fmt.Errorf("failed to create transactor: %w", err)
}

crossL2Inbox, err := bindings.NewCrossL2InboxTransactor(predeploys.CrossL2InboxAddr, client)
if err != nil {
return fmt.Errorf("failed to create transactor: %w", err)
}

for {
select {
case <-r.tasksCtx.Done():
unsubscribe()
close(sentMessageCh)
return nil
case sentMessage := <-sentMessageCh:
transactor, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(int64(destinationChainID)))
if err != nil {
return fmt.Errorf("failed to create transactor: %w", err)
}

crossL2Inbox, err := bindings.NewCrossL2InboxTransactor(predeploys.CrossL2InboxAddr, client)
if err != nil {
return fmt.Errorf("failed to create transactor: %w", err)
}

identifier := sentMessage.Identifier()
msg := sentMessage.Message()
calldata, err := msg.EventData()
Expand All @@ -105,15 +103,10 @@ func (r *L2ToL2MessageRelayer) Start(indexer *L2ToL2MessageIndexer, clients map[
}
// Pad gas by 33%.
paddedGas := (gasEstimate / 3) + gasEstimate
mu.Lock()
defer mu.Unlock()
originalTransactorGasLimit := transactor.GasLimit
transactor.GasLimit = paddedGas

if _, err := crossL2Inbox.ExecuteMessage(transactor, *identifier, predeploys.L2toL2CrossDomainMessengerAddr, calldata); err != nil {
return fmt.Errorf("failed to execute message: %w", err)
}
transactor.GasLimit = originalTransactorGasLimit
}
}

Expand Down
2 changes: 1 addition & 1 deletion supersim.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func (s *Supersim) ConfigAsString() string {
fmt.Fprintln(&b, "-----------------------")
fmt.Fprintln(&b, "For more information see the explainer! ( https://docs.optimism.io/stack/protocol/interop/explainer )")
fmt.Fprintln(&b, "\nAdded Predeploy Contracts:")
fmt.Fprintf(&b, " - L2ToL2CrossDomainMessenger: %s\n", predeploys.L2CrossDomainMessenger)
fmt.Fprintf(&b, " - L2ToL2CrossDomainMessenger: %s\n", predeploys.L2toL2CrossDomainMessenger)
fmt.Fprintf(&b, " - CrossL2Inbox: %s\n", predeploys.CrossL2Inbox)
}

Expand Down

0 comments on commit 2177e9d

Please sign in to comment.