From ec565e36d41a24d1904c2c4bc89a4a2e49d8f5cf Mon Sep 17 00:00:00 2001 From: Muang Date: Sun, 21 Jun 2026 03:26:24 +0900 Subject: [PATCH 1/2] Document router-scoped compliance guarantees Constraint: Issue 17 is a security-boundary documentation task, not a broad code hardening pass.\nRejected: Attempting to block every non-router path in code | direct token, pool, wrapper, and custodian paths require product and issuer-level decisions first.\nConfidence: high\nScope-risk: narrow\nDirective: Do not describe Corner Store as globally enforcing every RWA movement unless non-router paths are separately restricted.\nTested: git diff --check; scripts/check.sh\nNot-tested: production ERC-3643 issuer modules, live venues, RFQ, order book, wrapper, or custodian enforcement. --- ARCHITECTURE.md | 29 ++++++++++++ DECISIONS.md | 59 +++++++++++++++++++++++++ docs/architecture/README.md | 9 ++++ docs/architecture/execution-routing.md | 16 +++++-- docs/architecture/token-and-identity.md | 5 +++ docs/security.md | 56 +++++++++++++++++++++++ 6 files changed, 170 insertions(+), 4 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 541042e..9dd0d03 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -37,6 +37,13 @@ Execution Integration Kit로 구성한다. Corner Store reference DEX는 이 공 - compliance evaluation은 venue 실행과 분리한다. - execution routing은 법률 규칙이나 matching 로직을 직접 소유하지 않는다. - venue adapter는 정책을 정의하지 않는다. +- Corner Store의 DEX-level compliance 보장은 `ExecutionRouter`가 실행한 + router-mediated trade에 한정한다. +- ERC-3643 직접 전송, 직접 pool/venue 호출, wrapper/vault/custodian을 통한 경제적 + 소유권 이전은 자동으로 Corner Store 4-Layer evaluation과 `commit()`을 거치지 + 않는다. +- Router 밖 경로는 발행자 token-level enforcement에 위임하거나, controlled + venue/settlement로 제한하거나, 명시적으로 제품 범위 밖으로 선언해야 한다. - 무거운 자료와 재량 판단은 오프체인, 검증·게이팅·집행은 온체인에 둔다. - `tools/deploy-v3`는 Corner Store 제품 코드와 분리된 vendored 인프라다. @@ -60,6 +67,28 @@ Execution Integration Kit로 구성한다. Corner Store reference DEX는 이 공 SDK와 reference DEX의 전체 실행 흐름은 제품 명세에, 세부 책임과 불변성은 `docs/architecture/`에 둔다. +보호되는 execution 경로: + +```text +ExecutionRouter + → ComplianceEngine.evaluate() + → Adapter + → Venue/Pool/RFQ + → ComplianceEngine.commit() +``` + +보호되지 않는 경로 예시는 다음과 같다. + +- ERC-3643 token 직접 `transfer` / `transferFrom` +- 사용자의 직접 AMM pool 또는 외부 venue 호출 +- Router를 통하지 않는 RFQ/Order Book settlement +- wrapper, vault, custodian, omnibus account 또는 offchain ledger를 통한 RWA + economic exposure 이전 + +이 경계는 제품 보증 문구에도 반영해야 한다. Corner Store는 모든 가능한 RWA 이동을 +전역적으로 통제한다고 주장하지 않고, router-mediated trade에 대해 DEX-level +compliance를 강제한다고 설명한다. + ## External Dependencies - Foundry / forge-std diff --git a/DECISIONS.md b/DECISIONS.md index f264800..b8e10ee 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -195,3 +195,62 @@ pair 거래에서는 `tokenIn`과 `tokenOut`을 각각 분류한다. 양쪽 모 - `docs/MVP-v2-multi-venue.md` - `docs/architecture/asset-manifest.md` - `docs/security.md` + +## D006 — Corner Store compliance 보장은 Router 경로에 한정한다 + +Date: 2026-06-21 + +### Context + +PR #12 이후 Corner Store 내부 실행 경로는 다음 경계를 갖는다. + +- regulated-regulated pair에서 양쪽 ACTIVE Manifest를 모두 평가한다. +- 누락된 Recipe reference는 fail-closed한다. +- AMM Adapter 실행은 Router-only다. +- Router caller는 `context.initiator`와 일치해야 한다. + +그러나 이런 보강은 Corner Store 경로를 거치는 거래에만 적용된다. 사용자가 +ERC-3643 token을 직접 전송하거나, AMM pool/RFQ settlement/wrapper/custodian을 +직접 사용하면 `ExecutionRouter`, `ComplianceEngine.evaluate()`와 +`ComplianceEngine.commit()`을 우회할 수 있다. + +### Decision + +현재 skeleton의 보안·제품 보장은 제한된 범위 모델로 정의한다. + +Corner Store는 router-mediated trade에 대해 DEX-level compliance를 강제한다. +Router 밖의 RWA 이동 또는 경제적 소유권 이전은 자동으로 Corner Store 4-Layer +evaluation과 stateful `commit()`을 거치지 않는다. + +Router 밖 경로는 production deployment에서 다음 중 하나로 처리해야 한다. + +- ERC-3643 token/compliance module이 핵심 제한을 직접 강제한다. +- end user가 직접 호출할 수 없는 controlled venue/settlement로 제한한다. +- 제품 문서와 사용자-facing 설명에서 명시적으로 out-of-scope로 선언한다. + +### Alternatives Considered + +- Router-exclusive model을 즉시 확정: 임의의 third-party pool과 직접 호출 가능한 + venue를 기술적으로 차단하는 방식이 아직 설계되지 않아 제외한다. +- Token-level enforcement model을 즉시 확정: ERC-3643 issuer module이 Corner + Store의 모든 Recipe, cap, venue와 surveillance 요구를 대체한다는 외부 운영 + 계약이 없어 제외한다. +- 모든 non-router path를 암묵적으로 안전하다고 취급: Corner Store 검사가 생략될 + 수 있어 제외한다. + +### Consequences + +- `docs/security.md`와 `ARCHITECTURE.md`는 protected path와 non-protected path를 + 명시해야 한다. +- RFQ/Order Book settlement와 future Adapter는 Router-only authorization 또는 + 동등한 호출자 제한을 merge 조건으로 가져야 한다. +- 직접 ERC-3643 transfer, 직접 venue call, wrapper/vault/custodian과 offchain + beneficial ownership transfer는 별도 제한·위임·out-of-scope 결정 전까지 Corner + Store 보장으로 표현하지 않는다. +- Stateful surveillance는 Router 경로에서만 완전성을 주장할 수 있다. + +### Related Files + +- `docs/security.md` +- `ARCHITECTURE.md` +- `docs/architecture/execution-routing.md` diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 48bac4e..cd7c823 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -61,6 +61,11 @@ ERC-3643 Token & Identity는 이 네 layer 중 하나가 아니라 그 아래에 - Manifest와 `UNREGULATED` 분류가 모두 없는 자산은 `UNKNOWN`으로 fail-closed한다. - `ACTIVE` Manifest의 누락·불완전 reference는 fail-closed다. - settlement 직전 최신 Manifest, actor와 operator 상태를 평가한다. +- Corner Store의 4-Layer compliance 보장은 `ExecutionRouter`를 통한 + router-mediated trade에 한정한다. +- ERC-3643 직접 전송, 직접 pool/venue 호출, wrapper/vault/custodian 이전과 + offchain beneficial ownership 이전은 별도 제한·위임·out-of-scope 결정 없이는 + Corner Store 보장으로 표현하지 않는다. - non-custodial Router와 Adapter에는 의도하지 않은 자산 잔액이 남지 않는다. - 무거운 자료와 재량 판단은 오프체인에 두고 승인된 결과만 온체인에 입력한다. @@ -80,6 +85,10 @@ SDK 공통 컴포넌트는 특정 venue나 Corner Store 배포 구성에 의존 구체 Adapter는 reference 구현이며, 제3의 DEX는 같은 interface를 구현해 자체 venue를 등록할 수 있다. +현재 skeleton의 보안 보장은 limited-scope model이다. Router를 거치지 않는 RWA +이동 또는 경제적 노출 이전은 발행자 token-level enforcement, controlled +venue/settlement 또는 명시적 out-of-scope 선언으로 별도 처리해야 한다. + 외부 협업 범위: - production 법률 기준과 Element 승인 diff --git a/docs/architecture/execution-routing.md b/docs/architecture/execution-routing.md index c117561..36013f0 100644 --- a/docs/architecture/execution-routing.md +++ b/docs/architecture/execution-routing.md @@ -48,11 +48,15 @@ Uniswap v3, RFQ와 Order Book의 구체 Adapter 및 Corner Store 배포 configur - Router는 Registry에 등록된 adapter와 venue만 신뢰한다. - Adapter 구현체 교체와 중단은 권한이 분리된 관리 작업이다. +- Adapter와 settlement contract는 Router-only authorization 또는 동등한 + 호출자 제한을 가져야 한다. - decision 자체가 유효해도 요청 parameter가 decision과 다르면 실행하지 않는다. -- Router를 지원 진입점으로 둔다고 표준 pool 직접 호출이 기술적으로 차단되는 것은 - 아니다. -- Corner Store 4-Layer compliance 보장은 Router 지원 경로에 한정한다. 직접 pool - 호출은 Corner Store 지원 실행으로 간주하지 않는다. +- Router를 지원 진입점으로 둔다고 ERC-3643 직접 전송, 표준 pool 직접 호출, + wrapper/vault/custodian 이전 또는 offchain beneficial ownership 이전이 + 기술적으로 차단되는 것은 아니다. +- Corner Store 4-Layer compliance 보장은 Router 지원 경로에 한정한다. 직접 token + transfer, 직접 pool/venue 호출과 non-router settlement는 Corner Store 지원 + 실행으로 간주하지 않는다. ## Invariants @@ -61,6 +65,7 @@ Uniswap v3, RFQ와 Order Book의 구체 Adapter 및 Corner Store 배포 configur multi-Recipe 평가 결과를 얻은 뒤 Adapter를 호출한다. - Adapter에 전달하는 decision은 actor, token, amount, venue, version, expiry와 execution nonce에 바인딩한다. +- 실행 caller는 `context.initiator`와 일치해야 한다. - nonce 재사용과 deadline 초과 요청을 거부한다. - Router는 matching 로직과 법률 규칙을 포함하지 않는다. - Router에 의도하지 않은 사용자 자산 잔액이 남지 않는다. @@ -87,6 +92,8 @@ Uniswap v3, RFQ와 Order Book의 구체 Adapter 및 Corner Store 배포 configur - 표준 pool 직접 호출에는 ERC-3643 자체 transfer enforcement만 적용될 수 있다. 비우회 4-Layer enforcement가 필요한 production RWA venue는 별도 enforcement와 외부 승인이 확정되기 전 활성화하지 않는다. +- RFQ와 Order Book settlement는 Router-only authorization 또는 동등한 권한 모델이 + 확정되기 전 production-supported venue로 취급하지 않는다. ## Open Decisions @@ -94,6 +101,7 @@ Uniswap v3, RFQ와 Order Book의 구체 Adapter 및 Corner Store 배포 configur - 외부 preview API의 응답 형식 - nonce scope와 batch execution - adapter upgrade/replace governance +- Router-exclusive, token-level enforcement, limited-scope 중 production 보장 모델 - 일반 ERC-20 public venue fast path의 허용 venue와 관측 이벤트 범위 - 자산 classification onboarding과 integrator API diff --git a/docs/architecture/token-and-identity.md b/docs/architecture/token-and-identity.md index a4671bc..5c90648 100644 --- a/docs/architecture/token-and-identity.md +++ b/docs/architecture/token-and-identity.md @@ -49,6 +49,9 @@ Corner Store의 Manifest, `ComplianceDecision` 또는 venue를 생성·선택하 원자적으로 실패해야 한다. - ERC-3643 검사를 통과했다고 해서 venue, 거래 규모, 상대방, 운영자 조건이 자동으로 허용되는 것은 아니다. +- ERC-3643 직접 전송은 Corner Store `ExecutionRouter`, 4-Layer evaluation과 + stateful `commit()`을 자동으로 실행하지 않는다. 직접 전송 경로의 제한은 발행자 + token-level policy에 위임되거나 제품 범위 밖으로 명시되어야 한다. - RWA-RWA pair는 양쪽 토큰의 identity/compliance 조건을 각각 만족해야 한다. - 규제 토큰을 delist해도 일반 ERC-20 경로로 자동 전환하지 않는다. @@ -72,6 +75,8 @@ Corner Store의 Manifest, `ComplianceDecision` 또는 venue를 생성·선택하 - ERC-3643의 identity와 발행자 compliance 모듈을 재사용한다. - Corner Store는 발행 측 coverage를 중복하지 않는 execution-level compliance를 추가한다. +- Corner Store는 router-mediated trade에 대한 DEX-level compliance layer이며, + 모든 ERC-3643 token movement의 전역 enforcement layer로 표현하지 않는다. - AMM Pool처럼 자산을 보유하는 주소는 venue identity 등록 대상으로 본다. - Adapter가 토큰을 보관하지 않는 구조를 우선한다. diff --git a/docs/security.md b/docs/security.md index bae9e4f..ca3c398 100644 --- a/docs/security.md +++ b/docs/security.md @@ -5,11 +5,64 @@ - ERC-3643 / ONCHAINID의 identity와 token transfer enforcement는 외부 발행자 시스템의 책임이다. - Corner Store는 외부 판정을 임의로 완화하거나 우회하지 않는다. +- Corner Store의 DEX-level compliance 보장은 `ExecutionRouter`를 통한 실행 + 경로에 한정한다. Router 밖에서 발생하는 ERC-3643 직접 전송, 직접 venue 호출, + wrapper/vault/custodian을 통한 경제적 소유권 이전은 별도 제한, token-level + enforcement 위임 또는 명시적 out-of-scope 처리가 필요하다. - `tools/deploy-v3`는 vendored Uniswap v3 인프라이며 제품 compliance 보장을 제공하지 않는다. 세부 제품 경계는 `docs/architecture/`를 기준으로 한다. +## Compliance Enforcement Boundary + +지원되는 enforcement 경로: + +```text +ExecutionRouter + → ComplianceEngine.evaluate() + → Adapter + → Venue/Pool/RFQ + → ComplianceEngine.commit() +``` + +이 경로에서는 Router가 최신 Manifest와 applicable Recipe를 평가하고, 허용된 +venue/adapter에만 실행을 위임하며, 성공 후 stateful compliance `commit()`을 +호출한다. + +지원 경로 밖에서는 Corner Store의 4-Layer compliance가 자동으로 실행되지 않는다. +특히 다음 경로는 production-ready 보장으로 간주하지 않는다. + +- ERC-3643 token의 직접 `transfer` / `transferFrom` +- 사용자가 AMM pool 또는 외부 venue를 직접 호출하는 swap +- Router를 거치지 않는 RFQ 또는 Order Book settlement +- wrapper, vault, custodian 또는 omnibus account를 통한 경제적 소유권 이전 +- offchain ledger에서의 beneficial ownership 이전 + +이런 경로가 열려 있으면 다음 Corner Store 검사가 생략될 수 있다. + +- investor qualification Recipe +- amount cap과 offering/fund cap +- venue allowlist와 operator pause +- nonce/replay control +- `ComplianceEngine.commit()` 기반 surveillance/stateful Element update +- future lockup, affiliate, jurisdiction-specific rule + +현재 skeleton의 기본 보안 모델은 제한된 범위 모델이다. + +> Corner Store는 router-mediated trade에 대해 DEX-level compliance를 강제한다. +> Router 밖의 RWA 이동 또는 경제적 노출 이전은 발행자 token-level enforcement에 +> 위임되거나, 별도 controlled venue로 제한되거나, 제품 범위 밖으로 명시되어야 한다. + +더 강한 production 보장이 필요하면 다음 중 하나를 별도 설계 결정으로 확정해야 한다. + +- Router-exclusive model: end user가 직접 호출할 수 없는 controlled venue/settlement만 + 지원한다. +- Token-level enforcement model: 핵심 제한을 ERC-3643 compliance module이 Router + 밖에서도 강제한다. +- Limited-scope model: Corner Store 보장을 router-mediated trade로 한정하고, + non-router path는 문서와 제품 설명에서 out-of-scope로 명시한다. + ## Secrets - private key, RPC credential과 API token을 코드나 문서에 커밋하지 않는다. @@ -27,6 +80,7 @@ ## Input Validation - 외부 주소, amount, deadline, nonce, manifest version과 venue context를 검증한다. +- Router 실행 요청의 caller는 `context.initiator`와 일치해야 한다. - 명시적 `UNREGULATED` public path와 `ACTIVE` Manifest regulated path를 명시적으로 구분한다. - `ACTIVE` Manifest의 invalid Recipe/reference, unsupported engine와 version @@ -39,6 +93,8 @@ ## Asset Safety - Router와 Adapter는 의도하지 않은 자산을 보관하지 않는 구조를 우선한다. +- Adapter와 settlement contract는 Router-only authorization 또는 동등한 호출자 + 제한을 가져야 한다. - 실패한 실행은 nonce, fill accounting과 token balance를 원자적으로 되돌려야 한다. - ERC-3643 transfer 실패를 성공으로 취급하거나 swallow하지 않는다. From 4b8721679c0f33c44f0cdec7aba306d0e663bfdf Mon Sep 17 00:00:00 2001 From: Muang Date: Sun, 21 Jun 2026 03:59:25 +0900 Subject: [PATCH 2/2] Enforce router-only venue stubs Constraint: Issue 17 requires skeleton-supported venue paths to avoid compliance bypass while non-router token/pool paths remain product-boundary decisions.\nRejected: Treating this as docs-only | added router-only guards and direct-call tests for future RFQ/order-book stubs.\nConfidence: high\nScope-risk: narrow\nDirective: Future adapters and settlement contracts must keep router-only or equivalent authorization before production support.\nTested: forge fmt; git diff --check; scripts/check.sh\nNot-tested: live RFQ/order-book settlement, live Uniswap v3 pools, wrapper/vault/custodian enforcement. --- docs/security.md | 35 +++++++++++++++++++ .../adapters/amm/UniswapV3Adapter.sol | 5 ++- .../adapters/orderbook/OrderBookAdapter.sol | 24 +++++++++++-- src/execution/adapters/rfq/RFQAdapter.sol | 23 ++++++++++-- .../execution/IExecutionAdapter.sol | 4 +++ test/unit/execution/AdapterStubs.t.sol | 21 ++++++++++- 6 files changed, 106 insertions(+), 6 deletions(-) diff --git a/docs/security.md b/docs/security.md index ca3c398..0d574b8 100644 --- a/docs/security.md +++ b/docs/security.md @@ -95,9 +95,28 @@ venue/adapter에만 실행을 위임하며, 성공 후 stateful compliance `comm - Router와 Adapter는 의도하지 않은 자산을 보관하지 않는 구조를 우선한다. - Adapter와 settlement contract는 Router-only authorization 또는 동등한 호출자 제한을 가져야 한다. +- ERC-20 상호작용은 return value를 직접 가정하지 않고 `SafeERC20` 또는 동등한 + safe wrapper를 사용한다. - 실패한 실행은 nonce, fill accounting과 token balance를 원자적으로 되돌려야 한다. - ERC-3643 transfer 실패를 성공으로 취급하거나 swallow하지 않는다. +## Venue Integration Security + +- Uniswap-style callback은 등록되었거나 계산으로 검증한 pool에서만 수락한다. + callback `data`가 payer/token을 포함하더라도 callback origin 검증 없이 신뢰하지 + 않는다. +- Pool/venue 등록은 compliance 보장의 일부다. 잘못된 venue 또는 악성 adapter가 + 등록되면 Router를 타더라도 settlement 결과가 왜곡될 수 있으므로 governance와 + preflight 검증 대상이다. +- RFQ와 Order Book signature flow는 구현 전에 chain id, verifying contract, + maker/taker, token pair, venue, policy/manifest version, nonce와 expiry를 + binding해야 한다. +- Slippage, deadline과 amount cap은 서로 다른 축이다. `amountIn`, RWA 수량, + quote notional과 investor/fund/offering cap의 기준을 혼동하지 않는다. +- External call, callback, token transfer가 포함된 경로는 access control, + reentrancy, unchecked return, signature replay와 business-logic bypass를 함께 + 검토한다. + ## Logging - 민감한 identity 자료와 법률 문서를 온체인 event나 일반 로그에 기록하지 않는다. @@ -131,3 +150,19 @@ scripts/check.sh - Manifest lifecycle과 cumulative multi-Recipe test - `UNKNOWN`, explicit `UNREGULATED`와 regulated path 구분 test - unregulated-regulated와 regulated-regulated pair의 양쪽 Manifest 적용 test +- 모든 Adapter/RFQ/OrderBook settlement의 direct caller rejection test +- ERC-20 safe transfer behavior와 callback-origin validation test +- signed order/decision 도입 시 chain/domain/nonce/expiry replay test + +## Reference Security Inputs + +- ERC-3643 official documentation: permissioned tokens provide identity-backed + transfer checks, but Corner Store-specific venue, Recipe, amount cap와 + surveillance 보장을 자동으로 대체하지 않는다. +- OpenZeppelin `SafeERC20`: ERC-20 operation failure, false return과 no-return + token 처리를 위해 safe wrapper를 사용한다. +- Uniswap v3 integration references: pool/callback origin 검증과 audited periphery + pattern을 직접 venue adapter 설계의 기준으로 삼는다. +- OWASP Smart Contract Security Top 10 / SCSVS: access control, input validation, + unchecked external calls, reentrancy, signature replay와 business-logic risk를 + review checklist에 포함한다. diff --git a/src/execution/adapters/amm/UniswapV3Adapter.sol b/src/execution/adapters/amm/UniswapV3Adapter.sol index 99174a3..807289c 100644 --- a/src/execution/adapters/amm/UniswapV3Adapter.sol +++ b/src/execution/adapters/amm/UniswapV3Adapter.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.17; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {Governed} from "../../../auth/Governed.sol"; import {IAMMAdapter} from "../../../interfaces/execution/adapters/IAMMAdapter.sol"; import {IPool} from "../../../interfaces/execution/adapters/IPool.sol"; @@ -22,6 +23,8 @@ import {Errors} from "../../../libraries/Errors.sol"; /// - The callback `data` ABI-encodes `(address payer, address tokenIn)` where `payer` is the /// buyer who has approved this adapter to spend `tokenIn`. contract UniswapV3Adapter is IAMMAdapter, Governed { + using SafeERC20 for IERC20; + mapping(address => bool) public registeredPool; address public router; @@ -86,7 +89,7 @@ contract UniswapV3Adapter is IAMMAdapter, Governed { // The owed amount is the positive delta (pool is owed tokenIn). uint256 amountOwed = amount0Delta > 0 ? uint256(amount0Delta) : uint256(amount1Delta); - IERC20(tokenIn).transferFrom(payer, msg.sender, amountOwed); + IERC20(tokenIn).safeTransferFrom(payer, msg.sender, amountOwed); } function _decodeVenueData(bytes calldata venueData) diff --git a/src/execution/adapters/orderbook/OrderBookAdapter.sol b/src/execution/adapters/orderbook/OrderBookAdapter.sol index e7155a7..a741460 100644 --- a/src/execution/adapters/orderbook/OrderBookAdapter.sol +++ b/src/execution/adapters/orderbook/OrderBookAdapter.sol @@ -4,13 +4,33 @@ pragma solidity 0.8.17; import {IOrderBookAdapter} from "../../../interfaces/execution/adapters/IOrderBookAdapter.sol"; import {ExecutionRequest, ExecutionResult} from "../../../types/ExecutionTypes.sol"; import {ComplianceDecision} from "../../../types/ComplianceTypes.sol"; +import {Errors} from "../../../libraries/Errors.sol"; +import {Governed} from "../../../auth/Governed.sol"; /// @title OrderBookAdapter /// @notice Stub. Order-book execution is not implemented in the skeleton. -contract OrderBookAdapter is IOrderBookAdapter { +/// @dev Even as a stub, order-book settlement keeps the production security +/// invariant: fills may only be entered through the router, never directly +/// by maker/taker. +contract OrderBookAdapter is IOrderBookAdapter, Governed { + address public router; + + event RouterSet(address indexed router); + + modifier onlyRouter() { + if (msg.sender != router) revert Errors.NotAuthorized(); + _; + } + + function setRouter(address router_) external onlyOwner { + router = router_; + emit RouterSet(router_); + } + function execute(ExecutionRequest calldata, ComplianceDecision calldata) external - pure + view + onlyRouter returns (ExecutionResult memory) { revert("OrderBook: not implemented"); diff --git a/src/execution/adapters/rfq/RFQAdapter.sol b/src/execution/adapters/rfq/RFQAdapter.sol index 81898a3..b9a3344 100644 --- a/src/execution/adapters/rfq/RFQAdapter.sol +++ b/src/execution/adapters/rfq/RFQAdapter.sol @@ -4,13 +4,32 @@ pragma solidity 0.8.17; import {IRFQAdapter} from "../../../interfaces/execution/adapters/IRFQAdapter.sol"; import {ExecutionRequest, ExecutionResult} from "../../../types/ExecutionTypes.sol"; import {ComplianceDecision} from "../../../types/ComplianceTypes.sol"; +import {Errors} from "../../../libraries/Errors.sol"; +import {Governed} from "../../../auth/Governed.sol"; /// @title RFQAdapter /// @notice Stub. RFQ execution is not implemented in the skeleton. -contract RFQAdapter is IRFQAdapter { +/// @dev Even as a stub, RFQ keeps the production security invariant: settlement +/// may only be entered through the router, never directly by maker/taker. +contract RFQAdapter is IRFQAdapter, Governed { + address public router; + + event RouterSet(address indexed router); + + modifier onlyRouter() { + if (msg.sender != router) revert Errors.NotAuthorized(); + _; + } + + function setRouter(address router_) external onlyOwner { + router = router_; + emit RouterSet(router_); + } + function execute(ExecutionRequest calldata, ComplianceDecision calldata) external - pure + view + onlyRouter returns (ExecutionResult memory) { revert("RFQ: not implemented"); diff --git a/src/interfaces/execution/IExecutionAdapter.sol b/src/interfaces/execution/IExecutionAdapter.sol index 6504b26..cbcaea3 100644 --- a/src/interfaces/execution/IExecutionAdapter.sol +++ b/src/interfaces/execution/IExecutionAdapter.sol @@ -5,6 +5,10 @@ import {ExecutionRequest, ExecutionResult} from "../../types/ExecutionTypes.sol" import {ComplianceDecision} from "../../types/ComplianceTypes.sol"; interface IExecutionAdapter { + /// @dev Security invariant: production adapters/settlement contracts MUST + /// only accept calls from ExecutionRouter or an equivalent authorized + /// dispatcher. Direct end-user calls bypass ComplianceEngine.evaluate(), + /// router nonce/venue checks, and ComplianceEngine.commit(). function execute(ExecutionRequest calldata req, ComplianceDecision calldata decision) external returns (ExecutionResult memory); diff --git a/test/unit/execution/AdapterStubs.t.sol b/test/unit/execution/AdapterStubs.t.sol index 90ac7d0..97ee2e1 100644 --- a/test/unit/execution/AdapterStubs.t.sol +++ b/test/unit/execution/AdapterStubs.t.sol @@ -5,15 +5,20 @@ import {Test} from "forge-std/Test.sol"; import {RFQAdapter} from "../../../src/execution/adapters/rfq/RFQAdapter.sol"; import {OrderBookAdapter} from "../../../src/execution/adapters/orderbook/OrderBookAdapter.sol"; import {ExecutionRequest} from "../../../src/types/ExecutionTypes.sol"; -import {ComplianceContext, ComplianceDecision, VenueType, FlowType} from "../../../src/types/ComplianceTypes.sol"; +import {ComplianceDecision} from "../../../src/types/ComplianceTypes.sol"; +import {Errors} from "../../../src/libraries/Errors.sol"; contract AdapterStubsTest is Test { RFQAdapter internal rfq; OrderBookAdapter internal ob; + address internal constant ROUTER = address(0xA17E); + function setUp() public { rfq = new RFQAdapter(); ob = new OrderBookAdapter(); + rfq.setRouter(ROUTER); + ob.setRouter(ROUTER); } function _req() internal pure returns (ExecutionRequest memory req) { @@ -23,13 +28,27 @@ contract AdapterStubsTest is Test { function test_rfq_notImplemented() public { ComplianceDecision memory d; + vm.prank(ROUTER); vm.expectRevert("RFQ: not implemented"); rfq.execute(_req(), d); } function test_orderbook_notImplemented() public { ComplianceDecision memory d; + vm.prank(ROUTER); vm.expectRevert("OrderBook: not implemented"); ob.execute(_req(), d); } + + function test_rfq_revertsDirectCaller() public { + ComplianceDecision memory d; + vm.expectRevert(Errors.NotAuthorized.selector); + rfq.execute(_req(), d); + } + + function test_orderbook_revertsDirectCaller() public { + ComplianceDecision memory d; + vm.expectRevert(Errors.NotAuthorized.selector); + ob.execute(_req(), d); + } }