Skip to content

Generic op interop sb #87

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: dev
Choose a base branch
from
Open
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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,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
7 changes: 6 additions & 1 deletion contracts/interfaces/ISocket.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.21;

import {ExecuteParams} from "../protocol/utils/common/Structs.sol";
import {ExecuteParams, ExecutionStatus} from "../protocol/utils/common/Structs.sol";

/**
* @title ISocket
* @notice An interface for a Chain Abstraction contract
Expand Down Expand Up @@ -76,11 +77,15 @@ interface ISocket {

function registerSwitchboard() external;

function chainSlug() external view returns (uint32);

/**
* @notice returns the config for given `plugAddress_` and `siblingChainSlug_`
* @param plugAddress_ address of plug present at current chain
*/
function getPlugConfig(
address plugAddress_
) external view returns (address appGateway, address switchboard);

function payloadExecuted(bytes32 payloadId_) external view returns (ExecutionStatus);
}
4 changes: 2 additions & 2 deletions contracts/protocol/socket/Socket.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ contract Socket is SocketUtils {
bytes calldata payload,
bytes32 params
) external returns (bytes32 callId) {
PlugConfig memory plugConfig = _plugConfigs[msg.sender];
PlugConfig memory plugConfig = plugConfigs[msg.sender];

// if no sibling plug is found for the given chain slug, revert
if (plugConfig.appGateway == address(0)) revert PlugDisconnected();
Expand All @@ -75,7 +75,7 @@ contract Socket is SocketUtils {
bytes memory transmitterSignature_
) external payable returns (bytes memory) {
if (executeParams_.deadline < block.timestamp) revert DeadlinePassed();
PlugConfig memory plugConfig = _plugConfigs[executeParams_.target];
PlugConfig memory plugConfig = plugConfigs[executeParams_.target];
if (plugConfig.appGateway == address(0)) revert PlugDisconnected();

bytes32 payloadId = _createPayloadId(plugConfig.switchboard, executeParams_);
Expand Down
35 changes: 35 additions & 0 deletions contracts/protocol/socket/SocketBatcher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import "../../interfaces/ISwitchboard.sol";
import "../utils/RescueFundsLib.sol";
import {ExecuteParams} from "../../protocol/utils/common/Structs.sol";
import "../../interfaces/ISocketBatcher.sol";
import {OpInteropSwitchboard} from "./switchboard/OpInteropSwitchboard.sol";

/**
* @title SocketBatcher
Expand Down Expand Up @@ -36,6 +37,40 @@ contract SocketBatcher is ISocketBatcher, Ownable {
return socket__.execute{value: msg.value}(executeParams_, transmitterSignature_);
}

function attestOPProveAndExecute(
ExecuteParams calldata executeParams_,
bytes32[] calldata previousPayloadIds_,
bytes32 digest_,
bytes calldata proof_,
bytes calldata transmitterSignature_
) external payable returns (bytes memory) {
OpInteropSwitchboard(executeParams_.switchboard).attest(
_createPayloadId(executeParams_),
digest_,
proof_
);
OpInteropSwitchboard(executeParams_.switchboard).proveRemoteExecutions(
previousPayloadIds_,
_createPayloadId(executeParams_),
transmitterSignature_,
executeParams_
);
return socket__.execute{value: msg.value}(executeParams_, transmitterSignature_);
}

function _createPayloadId(ExecuteParams memory executeParams_) internal view returns (bytes32) {
return
keccak256(
abi.encode(
executeParams_.requestCount,
executeParams_.batchCount,
executeParams_.payloadCount,
executeParams_.switchboard,
socket__.chainSlug()
)
);
}

function rescueFunds(address token_, address to_, uint256 amount_) external onlyOwner {
RescueFundsLib._rescueFunds(token_, to_, amount_);
}
Expand Down
6 changes: 3 additions & 3 deletions contracts/protocol/socket/SocketConfig.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ abstract contract SocketConfig is ISocket, AccessControl {
mapping(address => SwitchboardStatus) public isValidSwitchboard;

// plug => (appGateway, switchboard__)
mapping(address => PlugConfig) internal _plugConfigs;
mapping(address => PlugConfig) public plugConfigs;

// Error triggered when a connection is invalid
error InvalidConnection();
Expand Down Expand Up @@ -50,7 +50,7 @@ abstract contract SocketConfig is ISocket, AccessControl {
if (isValidSwitchboard[switchboard_] != SwitchboardStatus.REGISTERED)
revert InvalidSwitchboard();

PlugConfig storage _plugConfig = _plugConfigs[msg.sender];
PlugConfig storage _plugConfig = plugConfigs[msg.sender];

_plugConfig.appGateway = appGateway_;
_plugConfig.switchboard = switchboard_;
Expand All @@ -65,7 +65,7 @@ abstract contract SocketConfig is ISocket, AccessControl {
function getPlugConfig(
address plugAddress_
) external view returns (address appGateway, address switchboard) {
PlugConfig memory _plugConfig = _plugConfigs[plugAddress_];
PlugConfig memory _plugConfig = plugConfigs[plugAddress_];
return (_plugConfig.appGateway, _plugConfig.switchboard);
}
}
2 changes: 1 addition & 1 deletion contracts/protocol/socket/SocketUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ abstract contract SocketUtils is SocketConfig {
// Version string for this socket instance
bytes32 public immutable version;
// ChainSlug for this deployed socket instance
uint32 public immutable chainSlug;
uint32 public immutable override chainSlug;

uint64 public callCounter;

Expand Down
4 changes: 2 additions & 2 deletions contracts/protocol/socket/switchboard/FastSwitchboard.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ contract FastSwitchboard is SwitchboardBase {
* @param proof_ proof from watcher
* @notice we are attesting a digest uniquely identified with payloadId.
*/
function attest(bytes32 digest_, bytes calldata proof_) external {
function attest(bytes32 digest_, bytes calldata proof_) external virtual {
address watcher = _recoverSigner(keccak256(abi.encode(address(this), digest_)), proof_);

if (isAttested[digest_]) revert AlreadyAttested();
Expand All @@ -52,7 +52,7 @@ contract FastSwitchboard is SwitchboardBase {
/**
* @inheritdoc ISwitchboard
*/
function allowPacket(bytes32 digest_, bytes32) external view returns (bool) {
function allowPacket(bytes32 digest_, bytes32) external view virtual returns (bool) {
// digest has enough attestations
return isAttested[digest_];
}
Expand Down
234 changes: 234 additions & 0 deletions contracts/protocol/socket/switchboard/OpInteropSwitchboard.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
// 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";
import {ExecuteParams, ExecutionStatus} from "../../utils/common/Structs.sol";
import {IL2ToL2CrossDomainMessenger} from "optimism/interfaces/L2/IL2ToL2CrossDomainMessenger.sol";
import {Predeploys} from "optimism/src/libraries/Predeploys.sol";

contract OpInteropSwitchboard is FastSwitchboard {
struct RemoteEndpoint {
uint256 remoteChainId;
address remoteAddress;
}

// remoteChainSlug => remoteEndpoint
mapping(uint32 => RemoteEndpoint) public remoteEndpoints;
// remoteChainId => remoteAddress
mapping(uint256 => address) public remoteAddresses;

mapping(bytes32 => bool) public isSyncedOut;
mapping(bytes32 => bytes32) public payloadIdToDigest;
mapping(bytes32 => bytes32) public remoteExecutedDigests;
mapping(bytes32 => bool) public isRemoteExecuted;

error RemoteExecutionNotFound();
error DigestMismatch();
error PreviousDigestsHashMismatch();
error NotAttested();
error NotExecuted();
error CallerNotL2ToL2CrossDomainMessenger();
error InvalidCrossDomainSender();

event Attested(bytes32 payloadId, bytes32 digest, address watcher);

constructor(
uint32 chainSlug_,
ISocket socket_,
address owner_
) FastSwitchboard(chainSlug_, socket_, owner_) {}

function attest(bytes32 /*digest_*/, bytes calldata /*proof_*/) external pure override {
revert("Not implemented");
}

function attest(bytes32 payloadId_, bytes32 digest_, bytes calldata proof_) external {
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 allowPacket(
bytes32 digest_,
bytes32 payloadId_
) external view override returns (bool) {
// digest has enough attestations and is remote executed
return
payloadIdToDigest[payloadId_] == digest_ &&
isAttested[digest_] &&
isRemoteExecuted[payloadId_];
}

function syncOut(bytes32 payloadId_, uint32 remoteChainSlug_) external {
bytes32 digest = payloadIdToDigest[payloadId_];

// not attested
if (digest == bytes32(0) || !isAttested[digest]) revert NotAttested();

// already synced out
if (isSyncedOut[digest]) return;
isSyncedOut[digest] = true;

// not executed
ExecutionStatus isExecuted = socket__.payloadExecuted(payloadId_);
if (isExecuted != ExecutionStatus.Executed) revert NotExecuted();

_xMessageContract(
remoteEndpoints[remoteChainSlug_].remoteChainId,
remoteEndpoints[remoteChainSlug_].remoteAddress,
abi.encodeWithSelector(this.syncIn.selector, payloadId_, digest)
);
}

function syncIn(bytes32 payloadId_, bytes32 digest_) external {
if (msg.sender != Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) {
revert CallerNotL2ToL2CrossDomainMessenger();
}

address remoteAddress = IL2ToL2CrossDomainMessenger(
Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER
).crossDomainMessageSender();
uint256 remoteChainId = IL2ToL2CrossDomainMessenger(
Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER
).crossDomainMessageSource();

if (remoteAddresses[remoteChainId] != remoteAddress) {
revert InvalidCrossDomainSender();
}

remoteExecutedDigests[payloadId_] = digest_;
}

function proveRemoteExecutions(
bytes32[] calldata previousPayloadIds_,
bytes32 currentPayloadId_,
bytes calldata transmitterSignature_,
ExecuteParams memory executeParams_
) external {
// Calculate previousDigestsHash from stored remoteExecutedDigests
bytes32 previousDigestsHash = bytes32(0);
for (uint256 i = 0; i < previousPayloadIds_.length; i++) {
if (remoteExecutedDigests[previousPayloadIds_[i]] == bytes32(0))
revert RemoteExecutionNotFound();
previousDigestsHash = keccak256(
abi.encodePacked(previousDigestsHash, remoteExecutedDigests[previousPayloadIds_[i]])
);
}
// Check if the calculated previousDigestsHash matches the one in executeParams_
if (previousDigestsHash != executeParams_.prevDigestsHash)
revert PreviousDigestsHashMismatch();

address transmitter = _recoverSigner(
keccak256(abi.encode(address(socket__), currentPayloadId_)),
transmitterSignature_
);

// Construct current digest
(address appGateway, ) = socket__.getPlugConfig(executeParams_.target);
bytes32 constructedDigest = _createDigest(
transmitter,
currentPayloadId_,
appGateway,
executeParams_
);

bytes32 storedDigest = payloadIdToDigest[currentPayloadId_];
// Verify the constructed digest matches the stored one
if (storedDigest == bytes32(0) || !isAttested[storedDigest]) revert NotAttested();
if (constructedDigest != storedDigest) revert DigestMismatch();

isRemoteExecuted[currentPayloadId_] = true;
}

/**
* @notice creates the digest for the payload
* @param transmitter_ The address of the transmitter
* @param payloadId_ The ID of the payload
* @param appGateway_ The address of the app gateway
* @param executeParams_ The parameters of the payload
* @return The packed payload as a bytes32 hash
*/
function _createDigest(
address transmitter_,
bytes32 payloadId_,
address appGateway_,
ExecuteParams memory executeParams_
) internal view returns (bytes32) {
return
keccak256(
abi.encode(
transmitter_,
payloadId_,
executeParams_.deadline,
executeParams_.callType,
executeParams_.writeFinality,
executeParams_.gasLimit,
msg.value,
executeParams_.readAt,
executeParams_.payload,
executeParams_.target,
appGateway_,
executeParams_.prevDigestsHash
)
);
}

/**
* @notice creates the payload ID
* @param switchboard_ The address of the switchboard
* @param executeParams_ The parameters of the payload
*/
function _createPayloadId(
address switchboard_,
ExecuteParams memory executeParams_
) internal view returns (bytes32) {
// todo: match with watcher
return
keccak256(
abi.encode(
executeParams_.requestCount,
executeParams_.batchCount,
executeParams_.payloadCount,
switchboard_,
chainSlug
)
);
}

function _xMessageContract(
uint256 destChainId,
address destAddress,
bytes memory data
) internal {
IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER).sendMessage(
destChainId,
destAddress,
data
);
}

function addRemoteEndpoint(
uint32 remoteChainSlug_,
uint256 remoteChainId_,
address remoteAddress_
) external onlyOwner {
remoteEndpoints[remoteChainSlug_] = RemoteEndpoint({
remoteChainId: remoteChainId_,
remoteAddress: remoteAddress_
});
remoteAddresses[remoteChainId_] = remoteAddress_;
}

function removeRemoteEndpoint(uint32 remoteChainSlug_) external onlyOwner {
uint256 remoteChainId = remoteEndpoints[remoteChainSlug_].remoteChainId;
delete remoteEndpoints[remoteChainSlug_];
delete remoteAddresses[remoteChainId];
}
}
1 change: 1 addition & 0 deletions lib/optimism
Submodule optimism added at d3b8ea
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
hardhat-deploy/=node_modules/hardhat-deploy/
hardhat/=node_modules/hardhat/
solady/=lib/solady/src/
optimism/=lib/optimism/packages/contracts-bedrock/
Loading