Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ SEPOLIA_RPC="https://rpc.ankr.com/eth_sepolia/"
ARBITRUM_SEPOLIA_RPC="https://rpc.ankr.com/arbitrum_sepolia"
OPTIMISM_SEPOLIA_RPC="https://rpc.ankr.com/optimism_sepolia"
BASE_SEPOLIA_RPC="https://rpc.ankr.com/base_sepolia"
INTEROP_ALPH_0_RPC="https://interop-alpha-0.optimism.io"
INTEROP_ALPH_1_RPC="https://interop-alpha-1.optimism.io"

# EVMx key addresses
# Find the most up to date addresses in deployments/dev_addresses.json
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
[submodule "lib/solady"]
path = lib/solady
url = https://github.com/vectorized/solady
[submodule "lib/optimism"]
path = lib/optimism
url = https://github.com/ethereum-optimism/optimism
2 changes: 2 additions & 0 deletions contracts/base/AppGatewayBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,13 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway, FeesPlugin
_setAddressResolver(addressResolver_);
sbType = FAST;
}

/// @notice Sets the switchboard type
/// @param sbType_ The switchboard type
function _setSbType(bytes32 sbType_) internal {
sbType = sbType_;
}

/// @notice Creates a contract ID
/// @param contractName_ The contract name
/// @return bytes32 The contract ID
Expand Down
8 changes: 8 additions & 0 deletions contracts/interfaces/ISocket.sol
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ interface ISocket {
bytes payload;
}

enum ExecutionStatus {
NotExecuted,
Executed,
Reverted
}

/**
* @notice To call the appGateway on EVMx. Should only be called by a plug.
* @param payload_ bytes to be delivered to the Plug on EVMx
Expand Down Expand Up @@ -99,4 +105,6 @@ interface ISocket {
function getPlugConfig(
address plugAddress_
) external view returns (address appGateway, address switchboard);

function payloadExecuted(bytes32 payloadId_) external view returns (ExecutionStatus);
}
17 changes: 17 additions & 0 deletions contracts/interfaces/ISwitchboard.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ pragma solidity ^0.8.21;
* different blockchain networks.
*/
interface ISwitchboard {
struct PayloadParams {
bytes32 payloadId;
address appGateway;
address transmitter;
address target;
uint256 value;
uint256 deadline;
uint256 executionGasLimit;
bytes payload;
}

/**
* @notice Checks if a packet can be allowed to go through the switchboard.
* @param digest_ the packet digest.
Expand All @@ -16,4 +27,10 @@ interface ISwitchboard {
function allowPacket(bytes32 digest_, bytes32 packetId_) external view returns (bool);

function attest(bytes32 payloadId_, bytes32 digest_, bytes calldata proof_) external;

function syncOut(
bytes32 digest_,
bytes32 payloadId_,
PayloadParams calldata payloadParams_
) external;
}
4 changes: 2 additions & 2 deletions contracts/interfaces/IWatcherPrecompile.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ interface IWatcherPrecompile {
/// @dev Only callable by authorized addresses
function setOnChainContracts(
uint32 chainSlug_,
bytes32 sbType_,
address switchboard_,
address socket_,
address contractFactoryPlug_,
address feesPlug_
) external;

function setSwitchboard(uint32 chainSlug_, bytes32 sbType_, address switchboard_) external;

/// @notice Retrieves plug configuration for a specific network and plug
/// @param chainSlug_ The identifier of the network
/// @param plug_ The address of the plug
Expand Down
8 changes: 1 addition & 7 deletions contracts/protocol/socket/Socket.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,10 @@ contract Socket is SocketUtils {
////////////////////////////////////////////////////////////
uint64 public callCounter;

enum ExecutionStatus {
NotExecuted,
Executed,
Reverted
}

/**
* @dev keeps track of whether a payload has been executed or not using payload id
*/
mapping(bytes32 => ExecutionStatus) public payloadExecuted;
mapping(bytes32 => ExecutionStatus) public override payloadExecuted;

constructor(
uint32 chainSlug_,
Expand Down
40 changes: 33 additions & 7 deletions contracts/protocol/socket/SocketBatcher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "solady/auth/Ownable.sol";
import "../../interfaces/ISocket.sol";
import "../../interfaces/ISwitchboard.sol";
import "../utils/RescueFundsLib.sol";
import {ECDSA} from "solady/utils/ECDSA.sol";
import {AttestAndExecutePayloadParams} from "../../protocol/utils/common/Structs.sol";

/**
Expand All @@ -27,7 +28,7 @@ contract SocketBatcher is Ownable {

function attestAndExecute(
AttestAndExecutePayloadParams calldata params_
) external payable returns (bytes memory) {
) external payable returns (bytes memory returnData) {
ISwitchboard(params_.switchboard).attest(params_.payloadId, params_.digest, params_.proof);

ISocket.ExecuteParams memory executeParams = ISocket.ExecuteParams({
Expand All @@ -37,12 +38,37 @@ contract SocketBatcher is Ownable {
deadline: params_.deadline,
payload: params_.payload
});
return
socket__.execute{value: msg.value}(
params_.appGateway,
executeParams,
params_.transmitterSignature
);

returnData = socket__.execute{value: msg.value}(
params_.appGateway,
executeParams,
params_.transmitterSignature
);

address transmitter = _recoverSigner(
keccak256(abi.encode(address(socket__), params_.payloadId)),
params_.transmitterSignature
);
ISwitchboard.PayloadParams memory payloadParams = ISwitchboard.PayloadParams({
payloadId: params_.payloadId,
appGateway: params_.appGateway,
transmitter: transmitter,
target: params_.target,
value: 0,
deadline: params_.deadline,
executionGasLimit: params_.executionGasLimit,
payload: params_.payload
});
ISwitchboard(params_.switchboard).syncOut(params_.digest, params_.payloadId, payloadParams);
}

function _recoverSigner(
bytes32 digest_,
bytes memory signature_
) internal view returns (address signer) {
bytes32 digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", digest_));
// recovered signer is checked for the valid roles later
signer = ECDSA.recover(digest, signature_);
}

function rescueFunds(address token_, address to_, uint256 amount_) external onlyOwner {
Expand Down
8 changes: 7 additions & 1 deletion contracts/protocol/socket/switchboard/FastSwitchboard.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ contract FastSwitchboard is SwitchboardBase {
* there can be multiple proposals for same digest. To avoid need to re-attest for different proposals
* with same digest, we are storing attestations against digest instead of packetId and proposalCount.
*/
function attest(bytes32 payloadId_, bytes32 digest_, bytes calldata proof_) external {
function attest(bytes32 payloadId_, bytes32 digest_, bytes calldata proof_) external virtual {
address watcher = _recoverSigner(keccak256(abi.encode(address(this), digest_)), proof_);

if (isAttested[digest_]) revert AlreadyAttested();
Expand Down Expand Up @@ -78,4 +78,10 @@ contract FastSwitchboard is SwitchboardBase {
function registerSwitchboard() external onlyOwner {
socket__.registerSwitchboard();
}

function syncOut(
bytes32 digest_,
bytes32 payloadId_,
PayloadParams calldata payloadParams_
) external virtual {}
}
134 changes: 134 additions & 0 deletions contracts/protocol/socket/switchboard/OpInteropSwitchboard.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.21;

import {FastSwitchboard} from "./FastSwitchboard.sol";
import {SuperchainEnabled} from "./SuperchainEnabled.sol";
import {ISocket} from "../../../interfaces/ISocket.sol";

contract OpInteropSwitchboard is FastSwitchboard, SuperchainEnabled {
address public token;
address public remoteAddress;
uint256 public remoteChainId;

mapping(bytes32 => bool) public isSyncedOut;
mapping(bytes32 => bytes32) public payloadIdToDigest;
mapping(address => uint256) public unminted;

error OnlyTokenAllowed();

modifier onlyToken() {
if (msg.sender != token) revert OnlyTokenAllowed();
_;
}

constructor(
uint32 chainSlug_,
ISocket socket_,
address owner_
) FastSwitchboard(chainSlug_, socket_, owner_) {
if (chainSlug_ == 420120000) {
remoteChainId = 420120001;
} else if (chainSlug_ == 420120001) {
remoteChainId = 420120000;
}
}

function attest(bytes32 payloadId_, bytes32 digest_, bytes calldata proof_) external override {
address watcher = _recoverSigner(keccak256(abi.encode(address(this), digest_)), proof_);

if (isAttested[digest_]) revert AlreadyAttested();
if (!_hasRole(WATCHER_ROLE, watcher)) revert WatcherNotFound();

isAttested[digest_] = true;
payloadIdToDigest[payloadId_] = digest_;
emit Attested(payloadId_, digest_, watcher);
}

function syncOut(
bytes32 digest_,
bytes32 payloadId_,
PayloadParams calldata payloadParams_
) external override {
if (isSyncedOut[digest_]) return;
isSyncedOut[digest_] = true;

if (!isAttested[digest_]) return;

bytes32 digest = payloadIdToDigest[payloadId_];
if (digest != digest_) return;

bytes32 expectedDigest = _packPayload(payloadParams_);
if (expectedDigest != digest_) return;

ISocket.ExecutionStatus isExecuted = socket__.payloadExecuted(payloadId_);
if (isExecuted != ISocket.ExecutionStatus.Executed) return;

(address user, uint256 amount, bool isBurn) = _decodeBurn(payloadParams_.payload);

if (!isBurn) return;
_xMessageContract(
remoteChainId,
remoteAddress,
abi.encodeWithSelector(this.syncIn.selector, user, amount)
);
}

function syncIn(
address user_,
uint256 amount_
) external xOnlyFromContract(remoteAddress, remoteChainId) {
unminted[user_] += amount_;
}

function _decodeBurn(
bytes memory payload
) internal pure returns (address user, uint256 amount, bool isBurn) {
// Extract function selector from payload
bytes4 selector;
assembly {
// Load first 4 bytes from payload data
selector := mload(add(payload, 32))
}
// Check if selector matches burn()
if (selector != bytes4(0x9dc29fac)) return (user, amount, false);

// Decode the payload after the selector (skip first 4 bytes)
assembly {
user := mload(add(add(payload, 36), 0)) // 32 + 4 bytes offset for first param
amount := mload(add(add(payload, 68), 0)) // 32 + 4 + 32 bytes offset for second param
}
isBurn = true;
}

function _packPayload(PayloadParams memory payloadParams_) internal pure returns (bytes32) {
return
keccak256(
abi.encode(
payloadParams_.payloadId,
payloadParams_.appGateway,
payloadParams_.transmitter,
payloadParams_.target,
payloadParams_.value,
payloadParams_.deadline,
payloadParams_.executionGasLimit,
payloadParams_.payload
)
);
}

function checkAndConsume(address user_, uint256 amount_) external onlyToken {
unminted[user_] -= amount_;
}

function setToken(address token_) external onlyOwner {
token = token_;
}

function setRemoteAddress(address _remoteAddress) external onlyOwner {
remoteAddress = _remoteAddress;
}

function setRemoteChainId(uint256 _remoteChainId) external onlyOwner {
remoteChainId = _remoteChainId;
}
}
Loading
Loading