Skip to content
Merged
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
4 changes: 2 additions & 2 deletions FunctionSignatures.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@
| `isAttested` | `0xc13c2396` |
| `owner` | `0x8da5cb5b` |
| `ownershipHandoverExpiresAt` | `0xfee81cf4` |
| `processTrigger` | `0x7f3352bc` |
| `processPayload` | `0x7f3352bc` |
| `registerSwitchboard` | `0x74f5b1fc` |
| `renounceOwnership` | `0x715018a6` |
| `requestOwnershipHandover` | `0x25692962` |
Expand All @@ -389,7 +389,7 @@
| `owner` | `0x8da5cb5b` |
| `ownershipHandoverExpiresAt` | `0xfee81cf4` |
| `payloadCounter` | `0x550ce1d5` |
| `processTrigger` | `0x7f3352bc` |
| `processPayload` | `0x7f3352bc` |
| `registerSibling` | `0x4f58b88c` |
| `registerSwitchboard` | `0x74f5b1fc` |
| `renounceOwnership` | `0x715018a6` |
Expand Down
144 changes: 144 additions & 0 deletions PAYLOAD_ID_ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Payload ID Architecture - Unified Design

## Overview
Unified payload ID structure for all three payload types: Write, Trigger, and Message.

## Payload ID Structure

### Bit Layout (256 bits total)
```
[Origin: 64 bits][Verification: 64 bits][Pointer: 64 bits][Reserved: 64 bits]
```

Each component breakdown:
- **Origin (64 bits)**: `chainSlug (32 bits) | switchboardId/watcherId (32 bits)`
- **Verification (64 bits)**: `chainSlug (32 bits) | switchboardId/watcherId (32 bits)`
- **Pointer (64 bits)**: Counter value
- **Reserved (64 bits)**: For future extensibility

## Payload Type Specifications

### 1. Write Payloads (EVMX → On-chain)
- **Origin**: `evmxChainSlug (32) | watcherId (32)`
- Generated by: Watcher (on EVMX)
- Verified by: Watcher offchain (links source)
- **Verification**: `dstChainSlug (32) | dstSwitchboardId (32)`
- Generated by: Watcher (from config)
- Used by: Socket for routing
- **Pointer**: `payloadCounter (64)`
- Generated by: Watcher (switchboard-specific counter)

**Where Created**: `Watcher.sol` → `getCurrentPayloadId()`

### 2. Trigger Payloads (On-chain → EVMX)
- **Origin**: `srcChainSlug (32) | srcSwitchboardId (32)`
- Generated by: FastSwitchboard
- Verified by: Watcher offchain (verifies source)
- **Verification**: `evmxChainSlug (32) | watcherId (32)`
- Generated by: FastSwitchboard (from stored config)
- Used by: Socket for routing
- **Pointer**: `switchboardCounter (64)`
- Generated by: FastSwitchboard (switchboard-specific counter)

**Where Created**: `FastSwitchboard.sol` → `processPayload()`

### 3. Message Payloads (Plug → Plug)
- **Origin**: `srcChainSlug (32) | srcSwitchboardId (32)`
- Generated by: MessageSwitchboard
- Verified by: Destination switchboard (checks source)
- **Verification**: `dstChainSlug (32) | dstSwitchboardId (32)`
- Generated by: MessageSwitchboard
- Used by: Socket for routing
- **Pointer**: `switchboardCounter (64)`
- Generated by: MessageSwitchboard (switchboard-specific counter)

**Where Created**: `MessageSwitchboard.sol` → `_createDigestAndPayloadId()`

## Decoding and Verification

### Socket Verification (Destination)
1. Decode `payloadId` using `getVerificationInfo(payloadId)`
2. Extract `verificationChainSlug` and `verificationSwitchboardId`
3. Verify against local config:
- `verificationChainSlug == local chainSlug`
- `verificationSwitchboardId == local switchboardId`

### Source Verification (Off-chain Watcher)
1. Decode `payloadId` using `getOriginInfo(payloadId)`
2. Extract `originChainSlug` and `originId`
3. Verify source configuration matches expected values

### Payload Type Detection
- Check if `originChainSlug` or `verificationChainSlug` matches `evmxChainSlug`
- If `originChainSlug == evmxChainSlug`: **Write Payload**
- If `verificationChainSlug == evmxChainSlug`: **Trigger Payload**
- If neither: **Message Payload**

## Implementation Details

### IdUtils.sol Functions

#### Encoding
- `createPayloadId(originChainSlug, originId, verificationChainSlug, verificationId, pointer)`
- Creates new payload ID with all components

#### Decoding
- `decodePayloadId(payloadId)` - Full decode
- `getVerificationInfo(payloadId)` - Gets verification components (for Socket routing)
- `getOriginInfo(payloadId)` - Gets origin components (for source verification)

### Required Updates

1. **Watcher.sol**
- Update `getCurrentPayloadId()` to use new format
- Use `evmxSlug` as origin chain slug
- Use hardcoded `watcherId = 1` for now
- Get `dstSwitchboardId` from `switchboards` mapping

2. **FastSwitchboard.sol**
- Add state variables: `evmxChainSlug`, `watcherId` (with onlyOwner setters)
- Implement `processPayload()` to create payload ID
- Add counter: `uint64 public triggerPayloadCounter`
- Use: `origin = (chainSlug, switchboardId)`, `verification = (evmxChainSlug, watcherId)`

3. **MessageSwitchboard.sol**
- Update `_createDigestAndPayloadId()` to use new format
- Use: `origin = (chainSlug, switchboardId)`, `verification = (dstChainSlug, dstSwitchboardId)`

4. **Socket.sol**
- Update `execute()` to decode payload ID and verify verification components
- Remove old `createPayloadId` usage
- Use `getVerificationInfo()` to extract routing info

5. **SocketConfig.sol**
- Update `plugSwitchboardIds` type from `uint64` to `uint32` if needed (or keep uint64 and cast)

## Security Considerations

### Verification Flow
1. **Destination (Socket)**: Verifies verification component matches local config
2. **Source (Watcher offchain)**: Verifies origin component matches expected source
3. **Pointer verification**: Skipped for now (to be added later)

### Counter Management
- Each switchboard maintains its own counter
- Prevents cross-switchboard collisions
- Counters are monotonic (never decrease)

### ID Uniqueness
- Guaranteed by switchboard-specific counters
- Origin + Verification provide additional context
- Reserved bits allow future expansion without breaking changes

## Migration Notes

- No production deployments yet, so no migration needed
- All existing test code will need updates
- Backward compatibility not required

## Future Enhancements

- Add pointer verification mechanism
- Use reserved bits for additional metadata (payload version, flags, etc.)
- Support multiple watchers (remove hardcoded watcherId = 1)

25 changes: 10 additions & 15 deletions contracts/evmx/fees/Credit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -134,19 +134,13 @@ abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AppGatew
}

/// @notice Deposits credits and native tokens to a user
/// @param depositTo_ The address to deposit the credits to
/// @param chainSlug_ The chain slug
/// @param token_ The token address
/// @param nativeAmount_ The native amount
/// @param creditAmount_ The credit amount
function deposit(
uint32 chainSlug_,
address token_,
address depositTo_,
uint256 nativeAmount_,
uint256 creditAmount_
) external override onlyWatcher {
tokenOnChainBalances[chainSlug_][toBytes32Format(token_)] += creditAmount_ + nativeAmount_;
/// @param payload_ Encoded deposit parameters: (chainSlug, token, receiver, creditAmount, nativeAmount)
function deposit(bytes calldata payload_) external override onlyWatcher {
// Decode payload: (chainSlug, token, receiver, creditAmount, nativeAmount)
(uint32 chainSlug_, address token_, address depositTo_, uint256 creditAmount_, uint256 nativeAmount_) =
abi.decode(payload_, (uint32, address, address, uint256, uint256));

tokenOnChainBalances[chainSlug_][token_] += creditAmount_ + nativeAmount_;

// Mint tokens to the user
_mint(depositTo_, creditAmount_);
Expand All @@ -155,9 +149,10 @@ abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AppGatew
bool success = feesPool.withdraw(depositTo_, nativeAmount_);

if (!success) {
_mint(depositTo_, creditAmount_);
nativeAmount_ = 0;
// Convert failed native amount to credits
_mint(depositTo_, nativeAmount_);
creditAmount_ += nativeAmount_;
nativeAmount_ = 0;
}
}

Expand Down
19 changes: 9 additions & 10 deletions contracts/evmx/fees/FeesManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ contract FeesManager is Credit {
susdcSolanaProgramId = susdcSolanaProgramId_;
}


function setChainMaxFees(
uint32[] calldata chainSlugs_,
uint256[] calldata maxFees_
Expand Down Expand Up @@ -123,25 +122,25 @@ contract FeesManager is Credit {
/// @param assignTo_ The address of the transmitter
function unblockAndAssignCredits(
bytes32 payloadId_,
address assignTo_
address assignTo_,
uint256 amount_
) external override onlyWatcher {
uint256 blockedCredits_ = blockedCredits[payloadId_];
if (blockedCredits_ == 0) return;

address consumeFrom = watcher__().getPayload(payloadId_).consumeFrom;
Payload memory payload = watcher__().getPayload(payloadId_);
address consumeFrom = payload.consumeFrom;

// Unblock credits from the original user
userBlockedCredits[consumeFrom] -= blockedCredits_;
userBlockedCredits[consumeFrom] -= amount_;
blockedCredits[payloadId_] -= amount_;

// Burn tokens from the original user
_burn(consumeFrom, blockedCredits_);

_burn(consumeFrom, amount_);
// Mint tokens to the transmitter
_mint(assignTo_, blockedCredits_);
_mint(assignTo_, amount_);

// Clean up storage
delete blockedCredits[payloadId_];
emit CreditsUnblockedAndAssigned(payloadId_, consumeFrom, assignTo_, blockedCredits_);
emit CreditsUnblockedAndAssigned(payloadId_, consumeFrom, assignTo_, amount_);
}

function unblockCredits(bytes32 payloadId_) external override onlyWatcher {
Expand Down
60 changes: 24 additions & 36 deletions contracts/evmx/fees/MessageResolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ abstract contract MessageResolverStorage {

// Execution status enum
enum ExecutionStatus {
NotAdded, // Message not yet added
Pending, // Message added, awaiting execution
Executed // Payment completed
NotAdded, // Message not yet added
Pending, // Message added, awaiting execution
Executed // Payment completed
}

// slot 51
Expand All @@ -76,36 +76,41 @@ abstract contract MessageResolverStorage {
* @dev Uses Credits (ERC20) from FeesManager for payment settlement
* @dev Upgradeable proxy pattern with AddressResolverUtil
*/
contract MessageResolver is MessageResolverStorage, Initializable, AccessControl, AddressResolverUtil {
contract MessageResolver is
MessageResolverStorage,
Initializable,
AccessControl,
AddressResolverUtil
{
////////////////////////////////////////////////////////
////////////////////// ERRORS //////////////////////////
////////////////////////////////////////////////////////

/// @notice Thrown when watcher is not authorized
error UnauthorizedWatcher();

/// @notice Thrown when nonce has already been used
error NonceAlreadyUsed();

/// @notice Thrown when message is already added
error MessageAlreadyExists();

/// @notice Thrown when message is not found
error MessageNotFound();

/// @notice Thrown when message is not in pending status
error MessageNotPending();

/// @notice Thrown when payment transfer fails
error PaymentFailed();

/// @notice Thrown when sponsor has insufficient credits
error InsufficientSponsorCredits();

////////////////////////////////////////////////////////
////////////////////// EVENTS //////////////////////////
////////////////////////////////////////////////////////

/// @notice Emitted when message details are added
event MessageDetailsAdded(
bytes32 indexed payloadId,
Expand All @@ -118,22 +123,22 @@ contract MessageResolver is MessageResolverStorage, Initializable, AccessControl
uint256 feeAmount,
uint256 deadline
);

/// @notice Emitted when transmitter is paid
event TransmitterPaid(
bytes32 indexed payloadId,
address indexed sponsor,
address indexed transmitter,
uint256 feeAmount
);

/// @notice Emitted when message is marked as executed by watcher
event MessageMarkedExecuted(bytes32 indexed payloadId, address indexed watcher);

////////////////////////////////////////////////////////
////////////////////// CONSTRUCTOR /////////////////////
////////////////////////////////////////////////////////

constructor() {
_disableInitializers(); // disable for implementation
}
Expand Down Expand Up @@ -236,11 +241,7 @@ contract MessageResolver is MessageResolverStorage, Initializable, AccessControl
* @param signature_ Watcher signature confirming execution
* @param nonce_ Nonce to prevent replay attacks
*/
function markExecuted(
bytes32 payloadId_,
uint256 nonce_,
bytes calldata signature_
) external {
function markExecuted(bytes32 payloadId_, uint256 nonce_, bytes calldata signature_) external {
MessageDetails storage details = messageDetails[payloadId_];

// Verify message exists
Expand All @@ -251,12 +252,7 @@ contract MessageResolver is MessageResolverStorage, Initializable, AccessControl

// Create digest for signature verification
bytes32 digest = keccak256(
abi.encodePacked(
toBytes32Format(address(this)),
evmxChainSlug,
payloadId_,
nonce_
)
abi.encodePacked(toBytes32Format(address(this)), evmxChainSlug, payloadId_, nonce_)
);

// Recover signer from signature
Expand Down Expand Up @@ -286,12 +282,7 @@ contract MessageResolver is MessageResolverStorage, Initializable, AccessControl
if (!success) revert PaymentFailed();

emit MessageMarkedExecuted(payloadId_, watcher);
emit TransmitterPaid(
payloadId_,
details.sponsor,
details.transmitter,
details.feeAmount
);
emit TransmitterPaid(payloadId_, details.sponsor, details.transmitter, details.feeAmount);
}

////////////////////////////////////////////////////////
Expand Down Expand Up @@ -323,9 +314,7 @@ contract MessageResolver is MessageResolverStorage, Initializable, AccessControl
* @param payloadId_ Unique identifier for the payload
* @return Message details struct
*/
function getMessageDetails(
bytes32 payloadId_
) external view returns (MessageDetails memory) {
function getMessageDetails(bytes32 payloadId_) external view returns (MessageDetails memory) {
return messageDetails[payloadId_];
}

Expand Down Expand Up @@ -369,4 +358,3 @@ contract MessageResolver is MessageResolverStorage, Initializable, AccessControl
return messageDetails[payloadId_].status;
}
}

Loading