From 8ccd9aef2c1b3b6ad58a6f46fdd0185be565faea Mon Sep 17 00:00:00 2001 From: LEE JUNSEO Date: Sat, 4 Jul 2026 15:31:28 +0900 Subject: [PATCH] Bind execution requests to the registered venue type --- PROGRESS.md | 2 ++ src/execution/ExecutionRouter.sol | 8 +++++++- src/libraries/Errors.sol | 1 + test/unit/execution/Router.t.sol | 12 ++++++++++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/PROGRESS.md b/PROGRESS.md index 21e0e47..8d39a55 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -22,6 +22,8 @@ source of truth로 사용한다. - multi-venue 아키텍처와 책임 문서 작성 - Corner Store용 Uniswap v3 최소 배포 profile 분리와 테스트 - ExecutionRouter/VenueRegistry/VenueSelector와 AMM reference adapter skeleton +- router now rejects requests whose `context.venueType` mismatches the registered + `VenueConfig.venueType` (closes the PR-12 review medium finding) ## Blocked diff --git a/src/execution/ExecutionRouter.sol b/src/execution/ExecutionRouter.sol index 971f9aa..6931891 100644 --- a/src/execution/ExecutionRouter.sol +++ b/src/execution/ExecutionRouter.sol @@ -18,7 +18,7 @@ import {Events} from "../libraries/Events.sol"; /// @title ExecutionRouter /// @notice Single entry point for trade execution. Orchestrates the gate sequence /// (deadline -> nonce -> compliance -> amount bound -> venue suspension -> venue policy -/// binding -> adapter dispatch -> post-trade commit) and delegates the actual swap to +/// binding -> venue type binding -> adapter dispatch -> post-trade commit) and delegates the actual swap to /// the venue's registered adapter. Non-custodial: the router never holds tokens. contract ExecutionRouter is IExecutionRouter, Governed, ReentrancyGuard { IComplianceEngine public immutable engine; @@ -84,6 +84,12 @@ contract ExecutionRouter is IExecutionRouter, Governed, ReentrancyGuard { VenueConfig memory cfg = venueReg.venueOf(req.context.venue); if (!cfg.active || cfg.adapter == address(0)) revert Errors.AdapterNotRegistered(); + // 7a. bind the caller-supplied venue type to the registry's stored config. + // Step 6 validated req.context.venueType against the decision's type mask; + // without this check a caller could misreport venueType and route a venue + // of a disallowed type through an allowed-type bit. + if (cfg.venueType != req.context.venueType) revert Errors.VenueTypeMismatch(); + // 8. dispatch to adapter (performs the swap; non-custodial) ExecutionResult memory r = IExecutionAdapter(cfg.adapter).execute(req, d); diff --git a/src/libraries/Errors.sol b/src/libraries/Errors.sol index 6371c61..4ff69f4 100644 --- a/src/libraries/Errors.sol +++ b/src/libraries/Errors.sol @@ -6,6 +6,7 @@ library Errors { error PolicyNotActive(); // UNKNOWN/SUSPENDED error ComplianceRejected(bytes32 reasonCode); error VenueNotAllowed(); + error VenueTypeMismatch(); error VenueSuspended(); error AdapterNotRegistered(); error DeadlineExpired(); diff --git a/test/unit/execution/Router.t.sol b/test/unit/execution/Router.t.sol index 56a1c51..d30f780 100644 --- a/test/unit/execution/Router.t.sol +++ b/test/unit/execution/Router.t.sol @@ -206,6 +206,18 @@ contract RouterTest is Test { router.execute(_defaultReq()); } + function test_execute_revertsWhenVenueTypeMismatchesRegistry() public { + // registry has VENUE registered as VenueType.AMM (see setUp) + ExecutionRequest memory req = _defaultReq(); + req.context.venueType = VenueType.RFQ; + // let step-6 selector pass by allowing RFQ in the decision's type mask + engine.setDecision(_decision(true, 1 << uint256(VenueType.RFQ), bytes32(0), type(uint256).max, REASON_OK)); + + vm.prank(BUYER); + vm.expectRevert(Errors.VenueTypeMismatch.selector); + router.execute(req); + } + function test_revert_adapterNotRegistered_unknownVenue() public { ExecutionRequest memory req = _defaultReq(); req.context.venue = address(0xABCD); // not registered, but allowedVenuesHash==0 passes selector