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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ node_modules
ignition/deployments/chain-31337

/.idea
.devcontainer
198 changes: 198 additions & 0 deletions pod/IInbox.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

/// @title IInbox
/// @notice Cross-chain request/response inbox: send messages to remote chains, execute incoming calls, and query state.
/// @dev Fee-related fields on {Request} are **gas unit** budgets, not wei. See {InboxFeeManager}.
interface IInbox {
// --- Types ---

/// @notice Encoded method call and optional MPC ABI metadata.
struct MpcMethodCall {
/// @notice Function selector to re-encode with MPC GTs; zero means `data` is raw calldata.
bytes4 selector;
/// @notice ABI-encoded arguments or raw calldata.
bytes data;
/// @notice MPC datatype descriptors used by {MpcAbiCodec}.
bytes8[] datatypes;
/// @notice MPC ciphertext length descriptors used by {MpcAbiCodec}.
bytes32[] datalens;
}

/// @notice Stored outbound or incoming cross-chain request.
struct Request {
/// @notice Packed request id containing source chain id and nonce.
bytes32 requestId;
/// @notice Destination chain id for outbound requests, or source chain id for incoming requests.
uint256 targetChainId;
/// @notice Contract invoked on the destination/current chain.
address targetContract;
/// @notice Method call executed on the target.
MpcMethodCall methodCall;
/// @notice Immediate caller that submitted the request.
address callerContract;
/// @notice Application contract that should receive responses/errors.
address originalSender;
/// @notice Request creation or ingestion timestamp.
uint64 timestamp;
/// @notice Callback selector used for two-way success responses.
bytes4 callbackSelector;
/// @notice Error selector used for failed execution responses.
bytes4 errorSelector;
/// @notice True when a success response is expected.
bool isTwoWay;
/// @notice True after an incoming request or linked response has been processed.
bool executed;
/// @dev If this request is a one-way response or error delivery, links to the original two-way request ID.
bytes32 sourceRequestId;
/// @dev Gas unit budget for the remote execution leg (`call{gas: ...}` cap). Not wei.
uint256 targetFee;
/// @dev Gas unit budget for the callback leg on the source chain. Not wei.
uint256 callerFee;
}

/// @notice Response metadata for an incoming request.
struct Response {
/// @notice Outbound request id that delivered the response.
bytes32 responseRequestId;
/// @notice Response payload returned by the target contract.
bytes response;
}

/// @notice Stored execution or encoding error for a request.
struct Error {
/// @notice Request id that failed.
bytes32 requestId;
/// @notice Protocol-defined error code.
uint64 errorCode;
/// @notice Revert data or encoded error message.
bytes errorMessage;
}

/// @notice Active incoming execution context exposed to target contracts.
struct ExecutionContext {
/// @notice Remote chain that sent the current request.
uint256 remoteChainId;
/// @notice Remote contract that sent the current request.
address remoteContract;
/// @notice Current incoming request id.
bytes32 requestId;
}

// --- External: sends (payable) ---

/// @notice Send a two-way message with callback and error handlers on the remote chain.
/// @param targetChainId Destination chain ID.
/// @param targetContract Contract to call on the destination chain.
/// @param methodCall Calldata and MPC metadata.
/// @param callbackSelector Selector invoked on the source chain when the remote call succeeds.
/// @param errorSelector Selector invoked on the source chain when the remote call fails.
/// @param callbackFeeLocalWei Wei from `msg.value` reserved for the callback leg (converted to gas units in fee logic).
/// @return requestId The new outbound request ID.
function sendTwoWayMessage(
uint256 targetChainId,
address targetContract,
MpcMethodCall calldata methodCall,
bytes4 callbackSelector,
bytes4 errorSelector,
uint256 callbackFeeLocalWei
) external payable returns (bytes32);

/// @notice Send a one-way message with an error handler only (no callback).
/// @param targetChainId Destination chain ID.
/// @param targetContract Contract to call on the destination chain.
/// @param methodCall Calldata and MPC metadata.
/// @param errorSelector Selector invoked on error.
/// @return requestId The new outbound request ID.
function sendOneWayMessage(
uint256 targetChainId,
address targetContract,
MpcMethodCall calldata methodCall,
bytes4 errorSelector
) external payable returns (bytes32);

// --- External: execution (non-payable) ---

/// @notice Respond to the current incoming message (two-way flow).
/// @dev Gas for the return leg is **not** charged again: it uses the `callerFee` budget from the original two-way request.
/// @param data Payload routed to the original sender via `callbackSelector`.
function respond(bytes memory data) external;

/// @notice Signal an application error for the current incoming two-way message (same routing constraints as {respond}).
/// @dev Gas for the return leg is **not** charged again: it uses the `callerFee` budget from the original two-way request.
/// @param data ABI-encoded argument for the remote `errorSelector(bytes)`.
function raise(bytes memory data) external;

// --- External: views ---

/// @notice Return error details for a failed outgoing request.
/// @param requestId Outbound request ID.
/// @return code Error code.
/// @return message Error message or revert data.
function getOutboxError(bytes32 requestId) external view returns (uint256 code, string memory message);

/// @notice Return stored response bytes for a completed incoming flow.
/// @param requestId Incoming request ID.
/// @return response Response payload.
function getInboxResponse(bytes32 requestId) external view returns (bytes memory);

/// @notice Return a slice of outbound requests sent to `targetChainId`, in per-target nonce order.
/// @param targetChainId Destination chain whose outbound requests to read.
/// @param from Start index (0-based) within that target's sequence.
/// @param len Maximum number of requests to return.
/// @return requestsList Request structs.
function getRequests(uint256 targetChainId, uint256 from, uint256 len)
external
view
returns (Request[] memory);

/// @notice Total count of outbound requests issued from this inbox to `targetChainId`.
/// @param targetChainId Destination chain.
/// @return count Number of requests to that target.
function getRequestsLen(uint256 targetChainId) external view returns (uint256);

/// @notice Look up a stored outbound request by its id (id encodes source+target+nonce).
/// @param requestId Outbound request id.
/// @return request The stored request (zeroed if unknown).
function getRequest(bytes32 requestId) external view returns (Request memory);

/// @notice Look up a stored incoming request by its id (id encodes the source chain).
/// @param requestId Incoming request id.
/// @return request The stored incoming request (zeroed if unknown).
function getIncomingRequest(bytes32 requestId) external view returns (Request memory);

/// @notice Remote chain ID and contract for the currently executing incoming message.
/// @return chainId Remote chain ID.
/// @return contractAddress Remote caller contract.
function inboxMsgSender() external view returns (uint256 chainId, address contractAddress);

/// @notice Request ID for the currently executing incoming message.
/// @return requestId Active request ID.
function inboxRequestId() external view returns (bytes32);

/// @notice Source request ID linked from the current incoming message (if any).
/// @return sourceRequestId Linked request ID.
function inboxSourceRequestId() external view returns (bytes32);

// --- External: pure ---

/// @notice Pack source chain id (64 bits), target chain id (64 bits) and nonce (128 bits) into a request id.
/// @param sourceChainId Originating chain id.
/// @param targetChainId Destination chain id.
/// @param nonce Per-target nonce.
/// @return requestId 256-bit packed id.
function getRequestId(uint256 sourceChainId, uint256 targetChainId, uint256 nonce)
external
pure
returns (bytes32);

/// @notice Split a packed request id into source chain id, target chain id and nonce.
/// @param requestId Packed id.
/// @return sourceChainId Source chain id.
/// @return targetChainId Target chain id.
/// @return nonce Per-target nonce.
function unpackRequestId(bytes32 requestId)
external
pure
returns (uint256 sourceChainId, uint256 targetChainId, uint256 nonce);
}
53 changes: 53 additions & 0 deletions pod/IInboxMiner.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import "./IInbox.sol";

/// @title IInboxMiner
/// @notice Miner API: apply mined cross-chain payloads to this chain's inbox and withdraw fees.
interface IInboxMiner {
error RetryFailedRequestNotAFailedRequest();
error RequestIdRequired();
error RetryFailedRequestExecutionFailed(bytes returnData);
/// @notice The `sourceChainId` passed to {batchProcessRequests} is this chain's own id.
error SourceChainIsThisChain(uint256 chainId);
/// @notice A mined request's encoded source chain does not match the batch `sourceChainId`.
error RequestSourceChainMismatch(bytes32 requestId, uint256 expectedSourceChainId, uint256 actualSourceChainId);
/// @notice A mined request's encoded target chain is not this chain.
error RequestTargetChainMismatch(bytes32 requestId, uint256 expectedTargetChainId, uint256 actualTargetChainId);
/// @notice Inbound message processing is paused (circuit breaker).
error MessageProcessingPaused();

/// @notice Emitted when {retryFailedRequest} successfully re-executes a previously failed incoming request.
event RetryFailedRequestSuccess(bytes32 indexed requestId);
/// @notice Emitted when the owner toggles the message-processing circuit breaker.
event MessageProcessingPausedUpdated(bool paused);

/// @notice Mined inbound request. `targetFee` and `callerFee` are gas unit budgets (see {IInbox.Request}).
struct MinedRequest {
bytes32 requestId;
address sourceContract;
address targetContract;
IInbox.MpcMethodCall methodCall;
bytes4 callbackSelector;
bytes4 errorSelector;
bool isTwoWay;
bytes32 sourceRequestId;
uint256 targetFee;
uint256 callerFee;
}

/// @notice Validate and execute a batch of mined requests from `sourceChainId`.
/// @param sourceChainId Chain that produced the mined data.
/// @param mined Ordered requests to apply.
function batchProcessRequests(uint256 sourceChainId, MinedRequest[] memory mined) external;

/// @notice Withdraw accumulated native token fees to `to` (owner-only in concrete implementations).
function collectFees(address payable to) external;

/// @notice Pause or unpause inbound message processing (owner-only circuit breaker).
function setMessageProcessingPaused(bool paused) external;

/// @notice Re-execute a mined incoming request whose target call failed (e.g. OOG). Open to any payer for gas.
function retryFailedRequest(bytes32 requestId) external;
}
29 changes: 29 additions & 0 deletions pod/InboxUser.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import "./IInbox.sol";

/// @title InboxUser
/// @notice Mixin that restricts selected functions to the configured {IInbox} and exposes `inbox`.
abstract contract InboxUser {
/// @notice Cross-chain inbox used for messaging.
IInbox public inbox;

/// @notice Caller is not the configured inbox.
error OnlyInbox(address caller);

/// @notice Restrict a function to the configured inbox.
/// @dev Reverts unless `msg.sender` is the configured inbox.
modifier onlyInbox() {
if (msg.sender != address(inbox)) {
revert OnlyInbox(msg.sender);
}
_;
}

/// @notice Set the inbox contract (typically once from a constructor or initializer).
/// @param _inbox Inbox address.
function setInbox(address _inbox) internal {
inbox = IInbox(_inbox);
}
}
13 changes: 13 additions & 0 deletions pod/InboxUserCotiTestnet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import "./InboxUser.sol";
import "./PodNetworkConstants.sol";

/// @title InboxUserCotiTestnet
/// @notice Mixin that configures {InboxUser} for COTI testnet inbox address.
abstract contract InboxUserCotiTestnet is InboxUser {
constructor() {
setInbox(PodNetworkConstants.COTI_TESTNET_INBOX);
}
}
30 changes: 30 additions & 0 deletions pod/PodNetworkConstants.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

/// @title PodNetworkConstants
/// @notice Deployment constants used by chain-specific PoD dapp helper contracts.
library PodNetworkConstants {
/// @notice Deterministic inbox address shared by every chain (CreateX CREATE3 deploy).
/// @dev Same value on Sepolia, COTI testnet, and Avalanche Fuji because the CREATE3 address
/// depends only on the deployer EOA and salt (see scripts/createx.ts).
address internal constant INBOX = 0xAb625bE229F603f6BBF964474AFf6d5487e364De;

/// @notice Source-chain inbox used by PoD dapps on Sepolia.
address internal constant SEPOLIA_INBOX = INBOX;

/// @notice Avalanche Fuji chain id used as a source chain paired with COTI testnet.
uint256 internal constant AVALANCHE_FUJI_CHAIN_ID = 43113;

/// @notice Source-chain inbox used by PoD dapps on Avalanche Fuji.
address internal constant AVALANCHE_FUJI_INBOX = INBOX;

/// @notice COTI testnet chain id used for remote MPC execution.
uint256 internal constant COTI_TESTNET_CHAIN_ID = 7082400;

/// @notice COTI-side MPC executor paired with source-chain PoD dapps.
/// @dev Redeploy and update this after the deterministic inbox redeploy on COTI.
address internal constant COTI_TESTNET_MPC_EXECUTOR = 0xC76aaE4F3810fBBd5d96b92DEFeBE0034405Ad9c;

/// @notice COTI testnet inbox used by COTI-side dapps.
address internal constant COTI_TESTNET_INBOX = INBOX;
}
57 changes: 57 additions & 0 deletions pod/examples/MpcAdder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import "../../utils/mpc/MpcCore.sol";

import "../mpc/PodLib.sol";
import "../mpc/PodLibBase.sol";

/// @title MpcAdder
/// @notice Example dApp: 64-bit encrypted add over the inbox using {PodLib}.
contract MpcAdder is PodLib {
event AddRequest(bytes32 requestId);

ctUint64 private _result;

/// @notice Create an MPC adder bound to an inbox.
/// @param _inbox The inbox contract address.
constructor(address _inbox) PodLibBase(msg.sender) {
setInbox(_inbox);
}

/// @notice Send an MPC add request using encrypted inputs.
/// @param a Encrypted input a (itUint64).
/// @param b Encrypted input b (itUint64).
/// @param callbackFeeLocalWei Wei slice for callback leg; total inbox payment is `msg.value`.
function add(itUint64 calldata a, itUint64 calldata b, uint256 callbackFeeLocalWei) external payable {
bytes32 requestId = add64(
a,
b,
msg.sender,
MpcAdder.receiveC.selector,
PodLibBase.onDefaultMpcError.selector,
msg.value,
callbackFeeLocalWei
);
emit AddRequest(requestId);
}

/// @notice Receive the response and store the ciphertext result.
/// @param data The response payload containing the ciphertext.
function receiveC(bytes memory data) external virtual onlyInbox {
_receiveResult(data);
}

/// @dev Allows {MpcAdderPausable} to gate delivery without duplicating storage.
function _receiveResult(bytes memory data) internal virtual {
_result = abi.decode(data, (ctUint64));
}

/// @notice Return the last received ciphertext result.
function resultCiphertext() external view returns (ctUint64) {
return _result;
}
}



Loading