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
59 changes: 48 additions & 11 deletions IMPLEMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,42 @@ struct EncryptedIntent {
- [x] Decode AVS results (clearing price, matched volumes)
- [x] Execute swaps via PoolManager for matched intents
- [x] Handle partial fills
- [x] Implement fallback to normal v4 swap for unmatched intents
- [x] **Implement fallback to normal v4 swap for unmatched intents** ✅

### Fallback Mechanism (NEW - Completed)

#### Architecture
- **BatchResult Structure**: AVS returns both `settlements[]` and `matchedIndices[]`
- **Intent Tracking**: `intentProcessed[batchId][intentIndex]` mapping prevents double-processing
- **Automatic Fallback**: Unmatched intents automatically execute via Uniswap v4 pool

#### Implementation Details
- `_decodeIntent()`: Decodes mock encrypted intent parameters (zeroForOne, amountSpecified, sqrtPriceLimitX96)
- `_executeFallbackSwap()`: Triggers fallback swap via unlock pattern
- `_handleFallbackSwap()`: Executes swap within unlockCallback
- `_settleFallbackSwap()`: Handles token settlements for fallback swaps
- Updated `unlockCallback()`: Routes between batch settlement and fallback swaps
- Updated `MockAVS`: Returns BatchResult with matched indices

#### Flow
```
processBatchResult() called
1. Mark matched intents as processed
2. Execute net swap for matched settlements
3. Distribute tokens to matched users
4. Loop through all intents
→ If NOT processed → Execute fallback swap
→ Decode intent → Swap on Uniswap → Send tokens to user
```

### Enhanced Testing

- [x] Integration test with actual swaps
- [x] Test batch finalization triggers
- [x] Test AVS callback flow
- [x] **Test fallback mechanism** (testFallbackMechanism) ✅
- [x] **Test multiple unmatched intents** (testFallbackWithMultipleUnmatched) ✅
- [ ] Gas optimization benchmarks

### Security
Expand Down Expand Up @@ -190,10 +219,15 @@ forge test --gas-report
## Current Test Results

```
[PASS] testAVSProcessing() (gas: 30015)
[PASS] testBatchFinalization() (gas: 35883)
[PASS] testBatchIntentSubmission() (gas: 35360)
[PASS] testIntentSubmission() (gas: 104880)
Ran 6 tests for test/BatchAuction.t.sol:BatchAuctionTest
[PASS] testAVSProcessing() (gas: 662301)
[PASS] testBatchFinalization() (gas: 35828)
[PASS] testBatchIntentSubmission() (gas: 35350)
[PASS] testFallbackMechanism() (gas: 1315621) ✨ NEW
[PASS] testFallbackWithMultipleUnmatched() (gas: 1837570) ✨ NEW
[PASS] testIntentSubmission() (gas: 104831)

✅ 6 tests passed; 0 failed
```

## Key Innovations
Expand All @@ -206,13 +240,16 @@ forge test --gas-report

## Next Steps

1. Implement settlement logic in `processBatchResult()`
2. Add comprehensive swap integration tests
3. Optimize gas costs for batch operations
4. Begin AVS operator implementation
1. ~~Implement settlement logic in `processBatchResult()`~~ ✅ DONE
2. ~~Add comprehensive swap integration tests~~ ✅ DONE
3. ~~Implement fallback mechanism for unmatched intents~~ ✅ DONE
4. Gas optimization for batch operations
5. Begin real AVS operator implementation (Week 3)
6. Integrate Fhenix FHE encryption (Week 3)
7. Build frontend with Next.js + Fhenix SDK (Week 4)

---

**Status**: Week 1 Complete ✅
**Next Milestone**: Settlement Logic (Week 2)
**Status**: Week 2 Complete ✅ (Settlement + Fallback)
**Next Milestone**: Real EigenLayer AVS + Fhenix FHE Integration (Week 3)
**Target**: Hookathon Submission Ready by Week 4
181 changes: 168 additions & 13 deletions src/BatchAuctionHook.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,25 @@ contract BatchAuctionHook is BaseHook, IUnlockCallback {
int256 amount1;
}

struct BatchResult {
Settlement[] settlements; // Matched settlements from AVS
uint256[] matchedIndices; // Which intent indices were matched
}

struct DecodedIntent {
bool zeroForOne; // Swap direction
int256 amountSpecified; // Amount to swap
uint160 sqrtPriceLimitX96; // Price limit
}

// Batch configuration
uint256 public constant MAX_BATCH_SIZE = 100;
uint256 public constant BATCH_TIMEOUT = 30 seconds;

mapping(uint256 => EncryptedIntent[]) public batchIntents;
mapping(uint256 => uint256) public batchStartTime;
mapping(uint256 => bool) public batchFinalized;
mapping(uint256 => mapping(uint256 => bool)) public intentProcessed; // batchId => intentIndex => processed

uint256 public currentBatchId;
address public avsOracle;
Expand All @@ -43,6 +55,7 @@ contract BatchAuctionHook is BaseHook, IUnlockCallback {
event BatchFinalized(uint256 indexed batchId, uint256 intentCount);
event BatchProcessed(uint256 indexed batchId, bytes avsResult);
event BatchSettled(uint256 indexed batchId, int256 net0, int256 net1);
event FallbackExecuted(uint256 indexed batchId, uint256 intentIndex, address user, int256 amount0Delta, int256 amount1Delta);

constructor(IPoolManager _poolManager) BaseHook(_poolManager) {
batchStartTime[0] = block.timestamp;
Expand Down Expand Up @@ -147,44 +160,80 @@ contract BatchAuctionHook is BaseHook, IUnlockCallback {

emit BatchProcessed(batchId, avsResult);

Settlement[] memory settlements = abi.decode(avsResult, (Settlement[]));
// Decode batch result (settlements + matched indices)
BatchResult memory result = abi.decode(avsResult, (BatchResult));

// Calculate net flow
// Mark matched intents as processed
for (uint i = 0; i < result.matchedIndices.length; i++) {
intentProcessed[batchId][result.matchedIndices[i]] = true;
}

// Calculate net flow from matched settlements
int256 net0 = 0;
int256 net1 = 0;
for (uint i = 0; i < settlements.length; i++) {
net0 += settlements[i].amount0;
net1 += settlements[i].amount1;
for (uint i = 0; i < result.settlements.length; i++) {
net0 += result.settlements[i].amount0;
net1 += result.settlements[i].amount1;
}

// Execute net swap if needed
if (net0 != 0 || net1 != 0) {
poolManager.unlock(abi.encode(net0, net1));
}

// Distribute funds to users
for (uint i = 0; i < settlements.length; i++) {
if (settlements[i].amount0 > 0) {
// Distribute funds to matched users
for (uint i = 0; i < result.settlements.length; i++) {
if (result.settlements[i].amount0 > 0) {
activePoolKey.currency0.transfer(
settlements[i].user,
uint256(settlements[i].amount0)
result.settlements[i].user,
uint256(result.settlements[i].amount0)
);
}
if (settlements[i].amount1 > 0) {
if (result.settlements[i].amount1 > 0) {
activePoolKey.currency1.transfer(
settlements[i].user,
uint256(settlements[i].amount1)
result.settlements[i].user,
uint256(result.settlements[i].amount1)
);
}
}

emit BatchSettled(batchId, net0, net1);

// Execute fallback swaps for unmatched intents
uint256 totalIntents = batchIntents[batchId].length;
for (uint i = 0; i < totalIntents; i++) {
if (!intentProcessed[batchId][i]) {
// Intent was not matched, execute fallback
_executeFallbackSwap(batchId, i);
}
}
}

function unlockCallback(
bytes calldata data
) external override returns (bytes memory) {
require(msg.sender == address(poolManager), "Only pool manager");

// Check if this is a fallback swap or normal batch settlement
bool isFallback;
assembly {
// Load first 32 bytes to check if it's a bool (fallback flag)
let firstWord := calldataload(data.offset)
isFallback := iszero(iszero(firstWord))
}

// Try to decode as fallback first
if (data.length > 64) {
// Likely a fallback call
(bool fallbackFlag, uint256 batchId, uint256 intentIndex, address user, DecodedIntent memory decoded) =
abi.decode(data, (bool, uint256, uint256, address, DecodedIntent));

if (fallbackFlag) {
return _handleFallbackSwap(batchId, intentIndex, user, decoded);
}
}

// Normal batch settlement
(int256 net0, int256 net1) = abi.decode(data, (int256, int256));

// Determine swap direction and amount
Expand Down Expand Up @@ -270,6 +319,112 @@ contract BatchAuctionHook is BaseHook, IUnlockCallback {
return "";
}

/// @notice Decode mock encrypted intent (for demo purposes only)
/// @dev In production, AVS would decrypt and return these parameters
function _decodeIntent(bytes memory ciphertext) internal pure returns (DecodedIntent memory) {
// For mock purposes, ciphertext is just abi.encode(bool, int256, uint160)
(bool zeroForOne, int256 amountSpecified, uint160 sqrtPriceLimitX96) =
abi.decode(ciphertext, (bool, int256, uint160));

return DecodedIntent({
zeroForOne: zeroForOne,
amountSpecified: amountSpecified,
sqrtPriceLimitX96: sqrtPriceLimitX96
});
}

/// @notice Execute fallback swap for an unmatched intent
/// @param batchId The batch ID
/// @param intentIndex The index of the unmatched intent
function _executeFallbackSwap(uint256 batchId, uint256 intentIndex) internal {
EncryptedIntent storage intent = batchIntents[batchId][intentIndex];

// Decode the intent parameters
DecodedIntent memory decoded = _decodeIntent(intent.ciphertext);

// Encode data for fallback unlock callback
bytes memory fallbackData = abi.encode(
true, // isFallback flag
batchId,
intentIndex,
intent.user,
decoded
);

// Execute fallback swap via unlock pattern
poolManager.unlock(fallbackData);

// Mark as processed
intentProcessed[batchId][intentIndex] = true;
}

/// @notice Handle fallback swap execution within unlock callback
function _handleFallbackSwap(
uint256 batchId,
uint256 intentIndex,
address user,
DecodedIntent memory decoded
) internal returns (bytes memory) {
// Execute the swap
SwapParams memory params = SwapParams({
zeroForOne: decoded.zeroForOne,
amountSpecified: decoded.amountSpecified,
sqrtPriceLimitX96: decoded.sqrtPriceLimitX96
});

BalanceDelta delta = poolManager.swap(activePoolKey, params, new bytes(0));

// Settle the swap
_settleFallbackSwap(user, delta);

emit FallbackExecuted(batchId, intentIndex, user, delta.amount0(), delta.amount1());

return "";
}

/// @notice Settle the fallback swap and transfer tokens to user
function _settleFallbackSwap(address user, BalanceDelta delta) internal {
// First settle debts (negative amounts), then take credits (positive amounts)

// Settle token0 debt
if (delta.amount0() < 0) {
poolManager.sync(activePoolKey.currency0);
activePoolKey.currency0.transfer(
address(poolManager),
uint256(int256(-delta.amount0()))
);
poolManager.settle();
}

// Settle token1 debt
if (delta.amount1() < 0) {
poolManager.sync(activePoolKey.currency1);
activePoolKey.currency1.transfer(
address(poolManager),
uint256(int256(-delta.amount1()))
);
poolManager.settle();
}

// Take token0 credit
if (delta.amount0() > 0) {
poolManager.take(
activePoolKey.currency0,
user,
uint256(int256(delta.amount0()))
);
}

// Take token1 credit
if (delta.amount1() > 0) {
poolManager.take(
activePoolKey.currency1,
user,
uint256(int256(delta.amount1()))
);
}
}

function getBatchIntents(
uint256 batchId
) external view returns (EncryptedIntent[] memory) {
Expand Down
Loading
Loading