diff --git a/.env.example b/.env.example index 2afbcc6..062242a 100644 --- a/.env.example +++ b/.env.example @@ -1,13 +1,36 @@ +# ── Root (hardhat: deploy, wire, demo tasks) ──────────────────────────────── +# Copy to .env (gitignored). This file is ONLY for the contract toolchain. +# The worker and the indexer have their own: worker/.env and indexer/.env. +# +# See DEPLOYMENT.md for the full ordered sequence. + +# REQUIRED — the OWNER / deployer key (0x + 64 hex). This is what hardhat deploys from, and it is +# the key that later calls `approvePacket` to release held packets. +# +# NOTE: worker/.env also has a variable named PRIVATE_KEY, but there it means the OPERATOR key. +# Do not copy this file into worker/ — giving the worker the owner key would let it approve the +# very packets it chose to withhold, which is exactly what the split exists to prevent. PRIVATE_KEY= + +# REQUIRED for a real deployment — the worker key's ADDRESS (not its private key). +# Leave unset only for a throwaway demo: owner and operator then collapse onto the deployer and +# the worker gains approval rights. The deploy script and `dvn:preflight` both warn about it. +OPERATOR_ADDRESS= + +# Public defaults work but rate-limit. Use your own endpoints for anything sustained. RPC_URL_BASE_SEPOLIA=https://sepolia.base.org RPC_URL_OPTIMISM_SEPOLIA=https://sepolia.optimism.io -# Populated after `lz:deploy` (Phase 8): +# Populated AFTER deploying ComplianceDVN on each chain, and required BEFORE wiring. +# `layerzero.config.ts` refuses to wire without them: wiring a placeholder succeeds silently and +# then every message on that pathway is permanently unverifiable. DVN_BASE_SEPOLIA= DVN_OPTIMISM_SEPOLIA= -# DVN operator / worker tuning: -DVN_CONFIRMATIONS=5 -POLL_MS=15000 -CHECKPOINT_PATH=.context/dvn-checkpoint.json -# Operator-controlled flagged address for the veto demo (address you hold the key for): + +# Optional — only read by the `demo:*` tasks, to show a vetoed transfer alongside a clean one. +# An address you hold the key for. The worker has its own TEST_DENYLIST, which is the one that +# actually causes the veto; this copy just drives the demo output. TEST_DENYLIST= + +# Worker tuning (DVN_CONFIRMATIONS, POLL_MS, CHECKPOINT_PATH, …) is NOT read here — it lives in +# worker/.env. It used to be listed in this file, where setting it had no effect. diff --git a/.eslintignore b/.eslintignore index 9d1951d..b120f3a 100644 --- a/.eslintignore +++ b/.eslintignore @@ -14,4 +14,10 @@ deployments # Standalone Node 20 worker package: governed by its own toolchain # (worker/tsconfig.json typecheck + vitest), not the root Next.js eslint config. -worker \ No newline at end of file +worker + +# Demo dashboard: plain browser-global scripts served statically — no modules, no Node, +# so the root TS/Next config has nothing true to say about them. +demo/dashboard +# Checked-in compiled artifacts (see demo/README.md) — generated, not hand-authored. +demo/prebuilt \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index f1af5db..5744ec0 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -25,8 +25,7 @@ body: - Contracts (contracts/ComplianceDVN.sol, on-chain veto) - Worker — service (worker/service.ts, verify/commit/veto loop) - Worker — assess / risk engine (worker/assess/) - - Worker — tracker (worker/tracker/) - - CLI (pnpm cli assess / trace) + - Demo dashboard (demo/dashboard/) - Deploy / wiring (hardhat tasks, layerzero.config.ts) - Build / tooling / CI - Documentation diff --git a/.gitignore b/.gitignore index ae4855d..51b608d 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,6 @@ docs/superpowers/ # presentation deck — kept local, not uploaded presentation/ + +# worker runtime state (scan checkpoint, deferred queue, approvals) — per-deployment, not source +worker/.context/ diff --git a/.prettierignore b/.prettierignore index abeaf7e..aa1f6cc 100644 --- a/.prettierignore +++ b/.prettierignore @@ -17,5 +17,11 @@ package.json # Generated deployment artifacts (ABIs, solcInputs) — not hand-authored source. deployments/ -# Standalone Node 20 worker package: formatted via its own toolchain, not the root. -worker/ \ No newline at end of file +# Standalone Node 20 packages: formatted via their own toolchains, not the root. +worker/ +indexer/ + +# Demo dashboard: hand-formatted static pages (2-space, embedded scripts); reformatting +# them to the root style is churn with no reader. Prebuilt artifacts are generated JSON. +demo/dashboard/ +demo/prebuilt/ \ No newline at end of file diff --git a/BRANCH_DIFF.md b/BRANCH_DIFF.md new file mode 100644 index 0000000..f5a15b0 --- /dev/null +++ b/BRANCH_DIFF.md @@ -0,0 +1,383 @@ +# `backend` ↔ `main` 차이 + +이 문서를 처음 읽는 사람을 위한 안내입니다. 마지막 확인: 2026-07-31. + +## ⚠️ 먼저 알아야 할 것: 아직 커밋되지 않았습니다 + +``` +backend 70ffd60 +main 70ffd60 ← 같은 커밋 +``` + +두 브랜치는 **완전히 동일한 커밋을 가리킵니다.** `git log main..backend`는 0건입니다. +아래에 설명하는 모든 변경은 작성자의 로컬 작업 트리에만 있고, 아직 커밋·푸시되지 않았습니다. + +**즉, 지금 `git checkout backend`를 해도 아무것도 받을 수 없습니다.** 이 문서는 "무엇이 올 +예정인가"의 설명서이고, 실제로 받으려면 작성자가 커밋·푸시한 뒤여야 합니다. + +작업 트리 규모: + +| 구분 | 수량 | +| -------------- | ----------------------------- | +| 추적 파일 수정 | 49 files, +3841 / −658 | +| 신규 파일 | 64개 (그중 38개가 `indexer/`) | + +--- + +## 한눈에 + +`main`은 LayerZero V2 커스텀 DVN 예제에 제재 목록 대조 정도가 붙은 상태입니다. +`backend`는 그것을 **리스크 엔진 + 외부 인덱서 + 온체인 감사 추적**으로 확장합니다. + +| 영역 | main | backend | +| ------------- | ------------------------------- | --------------------------------------------------------- | +| 판정 | 제재 주소 목록 대조 → 통과/거부 | 라벨 가중치 점수 + 소스 신뢰도 천장 → **4단계 행동** | +| 행동 | allow / block | allow / **delay** / **manual-review** / block | +| 보류 해제 | — | 시계(delay) 또는 **owner의 온체인 `approvePacket`** | +| 그래프 분석 | 없음 | 신규 `indexer/` 패키지 (Postgres, 최대 3-hop 근접성) | +| 감사 추적 | 없음 | 온체인 `RiskVerdict` 이벤트 → 인덱서 → Postgres → Grafana | +| 컨트랙트 검증 | 없음 | Sourcify v2 조회 → `unverified_contract` 라벨 | +| 관측 | Prometheus 메트릭 | + Grafana 대시보드 2개 (compose 프로파일) | +| 배포 | 로컬/예제 | **base-sepolia + optimism-sepolia 배포·와이어링 완료** | + +프로세스는 세 개이고, 서로 코드를 공유하지 않습니다. **서명된 HTTP 피드와 온체인 +이벤트로만** 연결됩니다. + +``` + 사용자 send ──▶ EndpointV2 ──▶ ComplianceDVN.assignJob ──▶ JobAssigned + ▲ │ 폴링(15s) + submitVerification ───┘ ▼ + recordVerdict / PacketApproved ┌──────────────┐ + ▲ │ worker │ + └────────── 판정 ◀─────────────│ (호스트) │ + └──────┬───────┘ + 서명된 피드 ◀────┘ HTTP :9091 + ┌──────────────┐ + ERC-20 Transfer, RiskVerdict ─── 폴링(15s) ───────────▶│ indexer + PG │ + │ (Docker) │ + └──────────────┘ +``` + +--- + +## 1. 컨트랙트 — ABI 파괴적 변경 (재배포 필수) + +`contracts/ComplianceDVN.sol` (+83 −5). **`submitVerification`의 시그니처가 바뀌었습니다.** + +```solidity +// main +function submitVerification(bytes calldata packetHeader, bytes32 payloadHash, uint64 confirmations) + +// backend ← 판정 결과가 검증 트랜잭션에 동승 +function submitVerification( + bytes calldata packetHeader, bytes32 payloadHash, uint64 confirmations, + uint8 action, uint16 score, uint256 reasonMask, bytes32 evidenceHash +) +``` + +컨트랙트는 업그레이더블이 아니므로 **기존 배포 주소를 재사용할 수 없습니다.** `dvn:preflight`가 +이 불일치를 감지해 알려줍니다. + +**생성자도 바뀌었습니다** — `assignJob`이 send library로 게이트되면서 `_sendUln` 파라미터가 +추가됐습니다 (`(_owner, _operator, _sendUln, _receiveUln, _fee)`). 게이트가 없으면 아무나 +`assignJob`을 호출해 임의 payloadHash로 `JobAssigned`를 발생시킬 수 있고, worker는 그걸 "우리 +일"로 믿고 남의 패킷을 심사·verify하며 operator 가스를 태우게 됩니다. 아래 §5의 기존 배포는 +이 게이트 이전 버전이므로 **재배포가 필요합니다** (`dvn:preflight`가 `sendUln()` 부재를 감지). + +신규 함수·이벤트: + +| 항목 | 권한 | 용도 | +| ------------------------------------------------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------- | +| `recordVerdict(payloadHash, action, score, reasonMask, evidenceHash)` | `onlyOperator` | 검증하지 **않은** 패킷의 판정을 감사 기록으로 남김. `allow`는 거부됨 (그건 `submitVerification`에 동승하므로) | +| `approvePacket(bytes32 payloadHash)` | **`onlyOwner`** | manual-review로 보류된 패킷을 사람이 해제 | +| `event RiskVerdict(payloadHash, action, score, reasonMask, evidenceHash)` | — | 감사 추적의 원천 | +| `event PacketApproved(payloadHash, approver)` | — | worker가 이걸 관측해 보류를 해제 | + +`approvePacket`이 `onlyOwner`인 것이 설계의 핵심입니다 — **worker(operator 키)가 자기가 보류한 +패킷을 스스로 풀 수 없습니다.** 두 키를 같게 쓰면 이 보호가 사라집니다. + +`ACTION_*` 상수는 `allow=0, delay=1, manual-review=2, block=3`이고, `reasonMask`는 추가 전용 +비트마스크입니다 (`worker/assess/verdict.ts`의 `REASON_BITS`가 정본). + +### ⚠️ `MyOFT.mint`는 테스트넷 전용입니다 + +`contracts/MyOFT.sol`에 데모용 `mint(address,uint256)`을 추가했습니다. **접근 제어가 전혀 +없습니다.** 누구나 무한히 발행할 수 있으므로 메인넷에 절대 이 상태로 올리면 안 됩니다. + +--- + +## 2. 신규 패키지 `indexer/` — 35 파일, TS 3,339줄 + +Docker + PostgreSQL로 도는 별도 프로세스. worker와 코드를 공유하지 않는 이유는 신뢰 +경계를 나누기 위해서입니다 — 인덱서의 결론은 **서명된 피드**로만 worker에 들어가고, worker는 +서명자 allowlist와 버전 단조 증가(리플레이 방지)를 확인합니다. + +하는 일: + +1. 체인 스캔 → `RiskVerdict` / `PacketApproved`(감사)와 ERC-20 `Transfer`(그래프 엣지) 수집 +2. reorg 처리 — 저장된 블록 해시가 노드와 다르면 `REORG_DEPTH`(32) 창을 통째로 롤백 +3. Sourcify v2로 컨트랙트 검증 여부 조회. 응답을 못 받은 `unknown`과 미검증 `unverified`를 + 구분하고 **라벨은 후자만** 만듭니다 +4. **최대 3-hop 근접성** — 홉마다 라벨·가중치가 다릅니다: outbound `sanctions_1hop`(70) / + `sanctions_2hop`(45) / `sanctions_3hop`(25), inbound 40/20/10, 믹서 60/35/20. 경로는 같은 + 체인·블록 비내림차순·단순 경로만 인정하고 최단 거리만 라벨링합니다. 주체 본인의 첫 outbound + 엣지를 제외한 **모든 엣지는 `TOKEN_MINIMUMS` 이상**이어야 합니다 (dust로 임의 주소를 + 오염시키거나, 한 다리 건너 스미어하는 걸 막기 위함) +5. 10분마다 피드 빌드 → 정수만 쓰는 canonical JSON + EIP-191 서명 → `:9091`로 서빙 + +깊이는 **3홉까지**입니다 (`GRAPH_DEPTH = 3`, 정책 v2). 3-hop 라벨은 단독으로는 어떤 행동도 +일으키지 않고(25 < delay 임계 30) 다른 신호와 합산될 때만 작용합니다. + +--- + +## 3. worker 확장 — 33 파일 수정, 14 파일 신규 (+2,959 −466) + +`main`에도 worker는 있었습니다. 판정 부분이 통째로 교체되고 보류 큐가 새로 생겼습니다. + +신규 모듈: + +| 파일 | 역할 | +| ------------------------------ | ------------------------------------------------------------------- | +| `assess/policy.ts` | **정책 정본.** 라벨 가중치, 임계값, 딜레이 타이밍, `POLICY_VERSION` | +| `assess/verdict.ts` | 온체인 인코딩 — `ACTION_CODES`, `REASON_BITS`, `evidenceHash` | +| `assess/sources.ts` | 소스별 신뢰도 / `EnforcementLevel` | +| `assess/providers/contract.ts` | `getCode`, EIP-1967 프록시 슬롯 검사 | +| `assess/providers/token.ts` | `token()`/`symbol()`/`decimals()` → 가짜 스테이블코인 판별 | +| `assess/ingest/feed.ts` | 인덱서 피드 수신 — 서명 검증, 리플레이 방지 | +| `assess/canonical.ts` | 서명 대상 canonical JSON | +| `chain/reader.ts` | TTL 캐시 + 타임아웃이 붙은 체인 조회 | + +판정은 **두 개의 게이트**를 통과합니다: + +- **점수** — 라벨 가중치 합(100 상한) → 임계값 **90 / 60 / 30** → block / manual-review / delay / allow +- **천장** — 소스 신뢰도로 clamp. `DIRECT_HIT_LABELS`(`sanctions`, `sanctioned_mixer`, + `scam_token`, `operator_deny`)가 없으면 점수가 얼마든 **자동 차단까지 가지 못하고 + manual-review로 눌립니다.** "1홉 + 믹서 노출"은 강한 정황이지 확정이 아니라서, 추론만으로 + 자금을 묶지 않겠다는 판단입니다 + +`delay`는 5분 후 재심사이고 8회를 넘으면 manual-review로 승격됩니다. manual-review는 +**시계로 절대 풀리지 않고** owner의 `approvePacket`만이 해제합니다. + +### 환경변수 이름 변경 (주의) + +worker의 서명 키가 `PRIVATE_KEY` → **`OPERATOR_PRIVATE_KEY`** 로 바뀌었습니다. 루트 `.env`의 +`PRIVATE_KEY`는 owner 키이므로, 루트 파일을 worker로 복사하면 worker가 owner 권한을 갖게 +됩니다. 그래서 worker 서비스는 **환경에 `PRIVATE_KEY`나 `OWNER_PRIVATE_KEY`가 있으면 — +`OPERATOR_PRIVATE_KEY`가 함께 있어도 — 부팅을 거부하고 이유를 설명합니다.** + +`OWNER_PRIVATE_KEY`는 배포 셸에만 두세요. owner 액션(승인·거절)은 데모 대시보드에서 MetaMask +서명으로 이루어지므로, 어떤 서비스 환경에도 owner 키가 들어갈 일이 없습니다. + +--- + +## 4. 그 밖의 변경 + +- **`oapp.contract.ts`** (신규) — OApp 컨트랙트 이름을 한 곳에서 결정합니다. + 기존 `ToyOFT`의 delegate가 팀원 키(`0x69bd4d7e…210c`)라 `lz:oapp:wire`가 + `LZ_Unauthorized()`로 실패했습니다. 그래서 테스트용으로 `MyOFT`를 새로 배포해 쓰고 있고, + 되돌릴 때는 `OAPP_CONTRACT=ToyOFT` 하나만 바꾸면 됩니다 +- **`tasks/preflight.ts`** (신규, `dvn:preflight`) — 트랜잭션을 보내기 전에 서명자·RPC·잔액 + (실측 배포 가스 × 25 여유)·operator 분리·기존 배포 ABI 호환성·**OApp delegate 권한**을 + 검사합니다. 위 `LZ_Unauthorized`를 미리 잡아줍니다 +- **`tasks/verifyWiring.ts`** (신규, `dvn:verify-wiring`) — 온체인 `UlnConfig`를 디코딩해 + 우리 DVN이 send/receive 양쪽 `requiredDVNs`에 들어있는지 확인 +- **`test/hardhat/ComplianceDVN.test.ts`** (신규, 13 tests) — `forge`가 설치돼 있지 않아 + 실행 가능한 hardhat 테스트를 별도로 뒀습니다. foundry 테스트도 갱신했지만 미실행입니다 +- **`layerzero.config.ts`** — DVN 주소가 비어 있으면 0 주소로 기본값을 쓰지 않고 **throw** + 합니다. 0 주소로 와이어링하면 조용히 성공한 뒤 해당 경로의 모든 메시지가 영구히 검증 + 불가가 되기 때문입니다 +- **루트 `package.json`** — 쓰이지 않던 `zod@4`를 제거했습니다. LayerZero의 `zod ^3.22.4` + peer를 가로채 `lz:oapp:wire`가 `keyValidator._parse is not a function`으로 죽었습니다 +- **`DEPLOYMENT.md`** (신규) — 배포 순서. 아래 Quickstart가 이 문서를 가리킵니다 + +--- + +## 5. 배포된 주소 (테스트넷, 와이어링 완료) + +| 체인 | 컨트랙트 | 주소 | +| ---------------- | ------------- | -------------------------------------------- | +| base-sepolia | ComplianceDVN | `0x99DC27868af093Ee699Fb0ac4c952F19b298F5E2` | +| optimism-sepolia | ComplianceDVN | `0x99DC27868af093Ee699Fb0ac4c952F19b298F5E2` | +| base-sepolia | MyOFT | `0x81129e01913aBE10AB620B1fBB91820783DcDBf2` | +| optimism-sepolia | MyOFT | `0x81129e01913aBE10AB620B1fBB91820783DcDBf2` | + +⚠️ **위 주소들은 `assignJob` 게이트(§1) 이전 버전입니다.** 게이트를 적용하려면 재배포·재와이어링이 +필요하고, `dvn:preflight`가 이를 경고합니다. 그 외에도 **owner/operator 키는 작성자가 들고 +있으므로**, 직접 트랜잭션을 보내려면 어차피 본인 키로 재배포해야 합니다. + +동작 확인된 경로: + +- clean 전송 → `VERIFY submitted` → `COMMIT driven` → 도착 (`allow`, score 0) +- veto → `VETO — withholding verification` → 미배달 + `recordVerdict` 기록 (`block`, score 100) + +두 판정 모두 인덱서가 수집해 Postgres `risk_verdicts`에 남아 있습니다. + +--- + +## 6. Quickstart + +전체 배포 순서는 [DEPLOYMENT.md](DEPLOYMENT.md)에 있습니다. 여기서는 **이 브랜치를 처음 +받았을 때** 필요한 것만 적습니다. + +```bash +pnpm install +``` + +pnpm 워크스페이스 3개(루트 / `worker` / `indexer`)입니다. 루트와 두 패키지의 zod 버전이 +다른 것은 의도적입니다 — LayerZero가 zod 3을 요구합니다. + +**인덱서** (Docker): + +```bash +cd indexer +``` + +```bash +cp .env.example .env +``` + +`FEED_SIGNING_KEY`(서명 전용 새 키), `DVN_*`, `TRACKED_TOKENS`, `TOKEN_MINIMUMS`를 채웁니다. +`TOKEN_MINIMUMS`가 비면 inbound 라벨이 아예 생기지 않으니 주의하세요. + +```bash +docker compose up -d +``` + +**worker** (호스트에서 실행): + +```bash +cd worker +``` + +```bash +cp .env.example .env +``` + +`OPERATOR_PRIVATE_KEY`, `DVN_*`, `INDEXER_FEED_URL`, `INDEXER_SIGNERS`를 채웁니다. +피드 URL을 주면서 서명자 allowlist를 비우면 worker는 **부팅을 거부합니다.** + +```bash +pnpm start +``` + +**대시보드** (선택, `indexer/`에서): + +```bash +docker compose --profile observability up -d +``` + +Grafana — datasource와 대시보드가 +프로비저닝되어 있어 import가 필요 없습니다. worker 패널은 +. + +--- + +## 7. 검증 상태 + +| 대상 | 결과 | 명령 | +| ------------------- | -------------- | ----------------------------------------------------- | +| worker 단위 테스트 | **254 passed** | `cd worker && pnpm test` | +| indexer 단위 테스트 | **121 passed** | `cd indexer && pnpm test` | +| 컨트랙트 (hardhat) | **15 passed** | `npx hardhat test test/hardhat/ComplianceDVN.test.ts` | +| 타입 체크 | clean | 각 패키지 `pnpm typecheck` | + +`worker/`와 `indexer/`는 루트 prettier 대상이 **아닙니다** (`.prettierignore`에서 제외). +두 패키지에는 포맷터 스크립트가 없으므로 기준은 "주변 코드와 같게"입니다. 검증 게이트는 +`typecheck` + `test`입니다. + +**foundry 테스트는 실행되지 않았습니다** — `forge`가 이 환경에 설치돼 있지 않습니다. +`test/foundry/*.sol`은 새 ABI에 맞게 갱신했지만 미검증 상태입니다. + +--- + +## 8. 알려진 미구현 / 주의사항 + +외부 의존성 때문에 의도적으로 남긴 것: + +- **native 값 엣지** — 이벤트를 남기지 않으므로 trace API가 필요하고, 공용 RPC 대부분이 + 제공하지 않습니다 +- **`honeypot_suspect`** — 시뮬레이션이 필요합니다 +- **깊이 4 이상 순회** — 정책상 3홉 고정(정책 v2)이므로 미구현이 아니라 결정 사항입니다 + +엔드투엔드로 확인되지 **않은** 경로: + +- **manual-review → `approvePacket` 해제** — 피드 엔트리가 0이라 파생 라벨을 만들 수단이 + 없어서 실제로 굴려보지 못했습니다. 단위 테스트와 hardhat 테스트로는 덮여 있습니다 + +기타: + +- 루트 `tsc`에 기존 오류 2건 (`tasks/simple-workers-mock/wire.ts`의 `contractName` 누락). + 이 브랜치와 무관하며 손대지 않았습니다 +- worker가 fail-closed 프리즈로 체크포인트를 붙잡기 때문에, 다운타임이 RPC의 `getLogs` + 상한(base sepolia 2000블록 ≈ 67분)을 넘으면 예전에는 영구히 못 따라잡았습니다. 청킹으로 + 고쳤고 (`SCAN_CHUNK_BLOCKS`, 기본 2000) 회귀 테스트가 있습니다 + +--- + +## 9. 커밋 전 확인 사항 + +작성자가 푸시하기 전에 처리해야 합니다. + +- [x] **`.env` 3개 모두 gitignore됨** — 루트 / `worker` / `indexer` 확인했습니다. `indexer/`는 + 자체 `.gitignore` 없이 루트 패턴에 걸립니다 (`node_modules`, `.env` 모두 비앵커 패턴) +- [x] **`worker/.context/` gitignore 추가 완료** — 스캔 체크포인트·보류 큐·승인 목록이 든 + 런타임 상태 파일입니다. 추적된 적이 없어 `git rm --cached`는 필요 없었습니다 +- [x] `indexer/`에서 실제 커밋될 파일 38개 전수 확인 — 전부 소스·설정이고 런타임 상태나 + 비밀은 없습니다 +- [ ] **작업 중 실제 테스트넷 개인키가 대화에 노출됐습니다.** 자산이 없는 새 지갑이지만 + 폐기하는 것을 권합니다 +- [ ] `deployments/*/MyOFT.json`, `solcInputs/*.json` — 이 리포지토리는 배포 기록을 추적하는 + 관례이므로 **포함**이 맞습니다 +- [ ] `TEST_DENYLIST`가 데모용 `0x…dEaD`로 남아 있습니다. 정리 후 재시작 권장 +- [ ] 커밋을 쪼갤 것: 컨트랙트 ABI 변경 / 리스크 엔진 / 인덱서 신규 / 관측·문서 정도로 + 나누면 리뷰가 가능합니다. 지금은 49 + 64 파일이 한 덩어리입니다 + +--- + +## 10. 데모용 가짜 스테이블코인 (테스트넷 전용) + +`contracts/mocks/FakeStablecoinMock.sol` — USDC 심볼을 주장하는 미끼 컨트랙트입니다. 가치가 없고 +스테이블코인도 아니며, 리스크 엔진의 사칭 탐지를 실제로 굴려보기 위한 것입니다. + +| 체인 | 주소 | +| ---------------- | -------------------------------------------- | +| base-sepolia | `0x1B48E40F971298b03B6AD0Ae3CA047CD11b7eA6e` | +| optimism-sepolia | `0x7Ec44363Fdaa7EEC9B49a858220Ff543Bcd43ac7` | + +한 컨트랙트로 두 신호를 보여줍니다 (실측 확인): + +- 그대로 두면 `fake_stablecoin_suspect` 65점 → **manual-review** +- `worker/.env`의 `SCAM_TOKENS`에 주소를 넣으면 `scam_token` 100점 → **block** + +수취인은 **도착 체인** 상태로 심사되므로, base→op 전송이면 op 쪽 주소를 수취인으로 지정해야 합니다. + +### 데모용 컨트랙트 두 개 (테스트넷 전용) + +| 컨트랙트 | 체인 | 주소 | 발화 신호 | +| -------------------- | ---------------- | -------------------------------------------- | ---------------------------------------------------------------------- | +| `FakeStablecoinMock` | base-sepolia | `0x1B48E40F971298b03B6AD0Ae3CA047CD11b7eA6e` | `fake_stablecoin_suspect` 65 → manual-review | +| `FakeStablecoinMock` | optimism-sepolia | `0x7Ec44363Fdaa7EEC9B49a858220Ff543Bcd43ac7` | (SCAM_TOKENS에 넣으면 `scam_token` 100 → block) | +| `RiskyProxyMock` | optimism-sepolia | `0x9771013D82dcC2bdb489B982B4f201FD698A15e6` | `upgradeable_proxy` 15 + `contract_admin_risk` 50 = 65 → manual-review | + +`RiskyProxyMock`은 EIP-1967 슬롯에 admin을 `0x…dEaD`(TEST_DENYLIST 주소)로 써 둡니다. 실권자가 +오염된 업그레이더블 컨트랙트를 흉내내는 것이고, 다른 admin으로 배포하려면 `RISKY_ADMIN`을 주면 +됩니다. + +### 차단이 채널을 막는 문제와 `skip` + +검증을 보류한 패킷은 그 nonce가 영구히 비어 있고, LayerZero 채널은 nonce를 순서대로만 처리하므로 +**뒤의 정상 메시지가 모두 갇힙니다** (`LZ_InvalidNonce`). 해소는 `EndpointV2.skip`이며 OApp의 +delegate(= owner)만 호출할 수 있습니다. 대시보드 "보류 패킷" 탭의 **메시지 채널 상태** 섹션이 +막힌 nonce를 찾아 owner 서명으로 건너뛰게 해 줍니다. + +### 데모 지연시간 설정 + +| 설정 | 파일 | 값 | +| --------------------------- | ------- | --------------------------------------------------------------- | +| `POLL_MS` | worker | 3000 | +| `SCAN_CONFIRMATIONS` | worker | 1 (온체인 증명값 `DVN_CONFIRMATIONS`=5는 ULN 요구조건이라 유지) | +| `FEED_REFRESH_MS` | worker | 5000 — 피드만 재수신 (전체 재빌드는 60초) | +| `POLL_MS` / `CONFIRMATIONS` | indexer | 5000 / 1 | +| `FEED_REBUILD_MS` | indexer | 60000 — 단, **새 엣지가 잡히면 즉시 재발행** | + +전송 → 판정 ≈ 3.5초, 그래프 라벨 반영 ≈ 10초. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 561c878..b333e47 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -76,8 +76,7 @@ need them for live deploy/wire/demo flows (see the README). | `contracts/` | `ComplianceDVN.sol` (the thin on-chain DVN) and friends | | `worker/service.ts` | Always-on worker: watches `JobAssigned`, verifies/commits or vetoes | | `worker/assess/` | Chain-independent risk engine — `assess()` / `combine()` over the merged denylist | -| `worker/tracker/` | Tier 1 observation-only route tracker | -| `worker/cli.ts` | `pnpm cli assess ` / `pnpm cli trace ` | +| `demo/` | Demo assets: dashboard, decoy contracts, mint script (see `demo/README.md`) | | `deploy/`, `tasks/` | Hardhat deploy scripts and operator tasks | | `test/` | Foundry (`*.t.sol`) and Hardhat/Vitest tests | | `layerzero.config.ts` | DVN wiring (required DVN, per-chain ULN config) | diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..2539006 --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,233 @@ +# Deployment — testnet + +Ordered sequence for bringing up the contracts, the indexer, and the worker. The order matters: +each step produces a value the next one needs, and two of the steps are hard to undo. + +For day-2 operations (alerts, key rotation, recovery) see [worker/RUNBOOK.md](worker/RUNBOOK.md). + +## Keys + +Three distinct keys. Keeping them separate is not hygiene, it is the design: + +| Key | Held by | Purpose | Must be funded | +| -------------------- | ------------------- | ----------------------------------------------------------- | ---------------- | +| **owner / deployer** | a human | deploys, and calls `approvePacket` to release held packets | yes, both chains | +| **operator** | the worker process | `submitVerification`, `commitVerification`, `recordVerdict` | yes, both chains | +| **feed signer** | the indexer process | signs published feeds | no | + +`approvePacket` is `onlyOwner` precisely so the worker cannot release the packets it chose to +withhold. If owner and operator are the same key, that protection is gone — the worker could +approve its own holds. The deploy script warns when they collapse. + +The owner key belongs in the CLI environment as `OWNER_PRIVATE_KEY`, **never** in the worker's +environment. + +## 1. Root environment + +```bash +cp .env.example .env +``` + +Set in `.env`: + +- `PRIVATE_KEY` — the **owner/deployer** key (this is what hardhat deploys from) +- `OPERATOR_ADDRESS` — the worker key's _address_ (not its private key) +- `RPC_URL_BASE_SEPOLIA`, `RPC_URL_OPTIMISM_SEPOLIA` — override the public defaults if you have + your own endpoints; public RPCs rate-limit and the indexer polls continuously + +Leave `DVN_BASE_SEPOLIA` and `DVN_OPTIMISM_SEPOLIA` empty for now — they are outputs of step 3. + +## 2. Preflight + +```bash +npx hardhat dvn:preflight --network base-sepolia +``` + +```bash +npx hardhat dvn:preflight --network optimism-sepolia +``` + +Checks the signer resolves, the RPC answers, the deployer is funded, the ReceiveUln is known, and +whether an existing deployment is compatible. Sends no transactions. Fix every `ERROR` and read +every `WARN` before continuing. + +## 3. Deploy the DVN + +Both chains. `OPERATOR_ADDRESS` must be set or owner and operator collapse. + +```bash +npx hardhat deploy --network base-sepolia --tags ComplianceDVN +``` + +```bash +npx hardhat deploy --network optimism-sepolia --tags ComplianceDVN +``` + +Then put the two addresses into `.env` as `DVN_BASE_SEPOLIA` and `DVN_OPTIMISM_SEPOLIA`. + +> A previously deployed ComplianceDVN cannot be reused. `submitVerification` gained verdict +> parameters and the contract is not upgradeable, so the old address exposes a different ABI. +> `dvn:preflight` detects this and says so. + +## 4. Wire the pathway + +This is the step that tells the ULN which DVN each pathway **requires**. + +```bash +npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts +``` + +`layerzero.config.ts` throws if either `DVN_*` is unset rather than defaulting to a placeholder — +wiring a zero address succeeds silently and then every message on that pathway is permanently +unverifiable, because the required DVN has no code to verify with. + +Confirm both sides: + +```bash +npx hardhat dvn:status --network base-sepolia +``` + +Check `operator` is the worker address and `owner` is yours. + +## 5. Indexer + +```bash +cd indexer +cp .env.example .env +``` + +Set: + +- `FEED_SIGNING_KEY` — a fresh key, used only for signing. Note its **address**; the worker needs it. +- `DVN_BASE_SEPOLIA`, `DVN_OPTIMISM_SEPOLIA` — from step 3 +- `TRACKED_TOKENS` — the ERC-20s whose transfers build the graph. **Empty means no edges**, so the + feed will be valid, signed, and empty. +- `TOKEN_MINIMUMS` — `chain:token:minValue` per token, in the token's smallest unit. **Without an + entry a token never produces an inbound label**, so leaving this empty turns + `sanctions_1hop_inbound` off entirely. Outbound labels are unaffected. +- `POLICY_VERSION` — must equal the worker's `POLICY_VERSION` in `worker/assess/policy.ts` (currently `1`) + +```bash +docker compose up -d +``` + +Migrations run at boot. Verify: + +```bash +curl -s localhost:9091/feed/latest.json | head -c 400 +``` + +A `503 no feed published yet` is expected until the first build (`FEED_REBUILD_MS`, default 10 min). +Check the logs for the boot warnings — they name any tracked token missing a threshold. + +Get the signer address for the next step: + +```bash +docker compose logs indexer | grep -i "indexer feed\|signer" +``` + +## 6. Worker + +```bash +cd worker +cp .env.example .env +``` + +Set: + +- `OPERATOR_PRIVATE_KEY` — the **operator** key (funded on both chains). The name differs from + the root `.env` deliberately: the worker rejects a bare `PRIVATE_KEY` and explains why, so + copying the root file here fails loudly instead of granting owner rights. +- `DVN_BASE_SEPOLIA`, `DVN_OPTIMISM_SEPOLIA` — from step 3 +- `INDEXER_FEED_URL` — e.g. `http://:9091/feed/latest.json` +- `INDEXER_SIGNERS` — the indexer's signer **address**. The worker refuses to boot with a feed URL + and no allowlist: ingesting unverified labels is worse than having none. +- `EMIT_VERDICT_EVENTS` — `block` by default, which means each veto costs a `recordVerdict` + transaction. Set it empty to record only `allow` (which rides along on `submitVerification` for + free). +- `TEST_DENYLIST` — an address you hold a key for, if you want to demo a veto + +Do **not** set `OWNER_PRIVATE_KEY` here. + +```bash +pnpm start +``` + +```bash +curl -s localhost:9090/readyz +``` + +The worker refuses to verify anything until it has a fresh risk store, so `readyz` failing at +first is the fail-closed design working, not a fault. + +## 7. Smoke test + +Send a clean transfer and watch it settle. `demo:send` is the convenience wrapper: + +```bash +npx hardhat demo:send --network base-sepolia --dst opt --to 0x --amount 1 +``` + +Or the full LayerZero task, which takes explicit eids rather than a network: + +```bash +npx hardhat lz:oft:send --src-eid 40245 --dst-eid 40232 --to 0x --amount 1 +``` + +The worker log should show `VERIFY submitted` then `COMMIT driven`. Or screen a specific +transaction without sending: + +```bash +pnpm cli verify baseSepolia 0x --dry-run +``` + +Then exercise a veto by putting a held address in `TEST_DENYLIST` and sending from it — expect +`VETO — withholding verification` and no delivery. + +To exercise the manual-review path, set `OWNER_PRIVATE_KEY` in your **shell** (not the worker's +env) and: + +```bash +pnpm cli pending +``` + +```bash +pnpm cli approve optimismSepolia 0x +``` + +## Cross-checks that bite later + +- **Operator gas on both chains.** `submitVerification` and `commitVerification` run on the + _destination_ chain, so a bidirectional pathway needs the operator funded on both. +- **`POLICY_VERSION` must match** between worker code and indexer env. A mismatched feed is + rejected whole, not reconciled — scores computed under different weights are not comparable. +- **Feed TTL vs rebuild interval.** `FEED_TTL_SEC` must exceed `FEED_REBUILD_MS` or a document can + expire before its replacement exists and screening flaps. Config enforces this. +- **Staleness vs refresh.** `MAX_DENYLIST_STALENESS_MS >= DENYLIST_REFRESH_MS`, likewise enforced. +- **`DEGRADED_MODE`.** Default `degrade` keeps verifying on OFAC/OpenSanctions alone when the feed + is unavailable. `halt` withholds everything instead. Decide deliberately. +- **Sourcify v1 is in a brownout.** The indexer targets v2; if you point `VERIFIER_URL` at a + self-hosted instance, make sure it serves `/v2/contract/{chainId}/{address}`. + +## Dashboards + +For a local stack, the indexer's compose file brings up Prometheus and Grafana with both dashboards +and both datasources already provisioned: + +```bash +docker compose --profile observability up -d +``` + +Grafana on (loopback only, anonymous viewer). See +[indexer/README.md](indexer/README.md) for what the panels mean. + +To wire them into existing monitoring instead, import: + +- `worker/deploy/grafana-dashboard.json` — Prometheus only +- `indexer/deploy/grafana-dashboard.json` — needs both Prometheus and the indexer's Postgres; + the audit trail lives in Postgres and is deliberately not exported as metrics + +Both pin their datasource variables to the uids `dvn-prometheus` / `dvn-postgres`; if yours are +named differently, repoint the variable once at the top of the dashboard. + +Alerts: `worker/deploy/k8s/prometheusrule.yaml`. diff --git a/README.md b/README.md index a6aa022..f6ca2f3 100644 --- a/README.md +++ b/README.md @@ -64,8 +64,8 @@ direct-hit lookup over a denylist merged from four sources: veto: blocked if any party is flagged. The live denylist built to 101 entries during the demo. -`worker/tracker/` adds the Tier 1 observation-only tracker. `cli trace ` -reconstructs a route via the LayerZero Scan API and colors each endpoint with `assess()`. +Screening results, held packets, and owner actions are all surfaced in the demo dashboard +(`demo/dashboard/`), which links each verdict to its LayerZero Scan route. ## Deployed contracts @@ -94,14 +94,11 @@ npx hardhat lz:deploy --ci --networks base-sepolia,optimism-sepolia --tags Comp npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts --ci npx hardhat dvn:status --network base-sepolia # sanity -# Run the worker (always-on) + send demos +# Run the worker (always-on) pnpm worker # screens both chains, verifies/commits or vetoes -npx hardhat demo:send --network optimism-sepolia --to --dst base # delivers -npx hardhat demo:send --network optimism-sepolia --to $TEST_DENYLIST --dst base # vetoed -# One-shot CLI -pnpm cli assess
-pnpm cli trace # Tier 1 route + risk coloring +# Demo dashboard (MetaMask sends, owner review, sanctions/graph views) +cd demo/dashboard && python serve.py # http://localhost:8080 — see demo/README.md ``` ## Tests @@ -111,8 +108,9 @@ Foundry (31): `ComplianceDVN` unit tests (fee, job, operator-gating, admin) and one reverts `commitVerification` with `LZ_ULN_Verifying`, so the recipient balance stays 0. -Vitest (16): `assess()`/`combine()`, the 81-byte header and OFT-message decoders, the -`JobAssigned` assignment filter, the durable checkpoint, and the tracker transform. +Vitest (worker + indexer): `assess()`/`combine()`, the 81-byte header and OFT-message decoders, +the `JobAssigned` assignment filter, the durable checkpoint, the deferred queue, the signed feed, +and the 3-hop proximity graph. ## CI / CD @@ -160,11 +158,11 @@ verifying other OApps' packets on the shared endpoint. contracts/ComplianceDVN.sol ComplianceDVN.t.sol + ComplianceDvnVeto.t.sol (veto proof) contracts/ToyOFT.sol demo OFT deploy/ hardhat-deploy scripts +demo/ demo assets: dashboard, decoy contracts, mint script (demo/README.md) layerzero.config.ts requiredDVNs = our DVN, both directions -tasks/ dvn:status, demo:send +tasks/ dvn:status, dvn:preflight, dvn:verify-wiring worker/assess/ Tier 0 risk engine (OFAC + OpenSanctions + mixers + test) -worker/chain/ header/message decoders, PacketSent scanner, verify/commit -worker/tracker/ Tier 1 LayerZero-Scan tracker -worker/service.ts always-on watcher (fail-closed) worker/cli.ts one-shot +worker/chain/ header/message decoders, PacketSent scanner +worker/service.ts always-on watcher (fail-closed) docs/superpowers/ design spec + implementation plan ``` diff --git a/contracts/ComplianceDVN.sol b/contracts/ComplianceDVN.sol index 94e08ff..d3fcd0e 100644 --- a/contracts/ComplianceDVN.sol +++ b/contracts/ComplianceDVN.sol @@ -11,25 +11,69 @@ import { IReceiveUlnE2 } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln /// attestation behind an operator key. Withholding `submitVerification` IS the veto. contract ComplianceDVN is ILayerZeroDVN, Ownable { address public operator; // off-chain worker key + address public sendUln; // SendUln302 on this chain — the only address allowed to assign jobs address public receiveUln; // ReceiveUln302 on this chain uint256 public fee; event JobAssigned(uint32 dstEid, bytes32 payloadHash, uint64 confirmations, address sender); event OperatorSet(address operator); + event SendUlnSet(address sendUln); event ReceiveUlnSet(address receiveUln); event FeeSet(uint256 fee); + /// @notice A held packet cleared for verification by the owner. Deliberately owner-only: + /// the worker holds only the operator key, so it cannot approve its own holds. + event PacketApproved(bytes32 indexed payloadHash, address approver); + + /// @notice The risk decision behind a packet's outcome. + /// @param payloadHash the packet this verdict is about + /// @param action ACTION_* below + /// @param score 0-100 risk score the action was derived from + /// @param reasonMask bitmask of reason codes; bit assignments are append-only and + /// documented in the worker's `assess/verdict.ts` + /// @param evidenceHash keccak256 of the canonical evidence document held off-chain + event RiskVerdict( + bytes32 indexed payloadHash, + uint8 action, + uint16 score, + uint256 reasonMask, + bytes32 evidenceHash + ); + + /// @dev Action codes. These are part of the event ABI: an indexer decoding old logs relies + /// on them, so the numbering is permanent. Kept in sync with the worker's ACTION_CODES. + uint8 public constant ACTION_ALLOW = 0; + uint8 public constant ACTION_DELAY = 1; + uint8 public constant ACTION_MANUAL_REVIEW = 2; + uint8 public constant ACTION_BLOCK = 3; + error NotOperator(); + error NotSendLibrary(); + error UnknownAction(uint8 action); + /// @dev Submitting a verification asserts the packet was allowed; any other action would be + /// a self-contradicting record. + error VerificationRequiresAllow(uint8 action); + /// @dev An allow rides along on `submitVerification`, so recording one separately would + /// double-report the same outcome. + error AllowNotSeparatelyRecorded(); modifier onlyOperator() { if (msg.sender != operator) revert NotOperator(); _; } - constructor(address _owner, address _operator, address _receiveUln, uint256 _fee) Ownable(_owner) { + constructor( + address _owner, + address _operator, + address _sendUln, + address _receiveUln, + uint256 _fee + ) Ownable(_owner) { require(_operator != address(0), "zero operator"); + require(_sendUln != address(0), "zero sendUln"); require(_receiveUln != address(0), "zero receiveUln"); operator = _operator; + sendUln = _sendUln; receiveUln = _receiveUln; fee = _fee; } @@ -44,6 +88,10 @@ contract ComplianceDVN is ILayerZeroDVN, Ownable { } function assignJob(AssignJobParam calldata _param, bytes calldata) external payable returns (uint256) { + // Only the send library assigns jobs. The worker treats a JobAssigned payloadHash as + // "this packet is ours to screen" and spends operator gas verifying it, so an open + // assignJob would let anyone point the worker at packets no one asked it to verify. + if (msg.sender != sendUln) revert NotSendLibrary(); // NOTE: SendUln302 calls assignJob WITHOUT forwarding value (msg.value == 0); the // messagelib accrues each worker's fee internally and workers withdraw separately // (see SendUlnBase._assignJobs). So we must NOT require msg.value >= fee here — doing @@ -52,12 +100,49 @@ contract ComplianceDVN is ILayerZeroDVN, Ownable { return fee; } + /// @notice Attest a packet and record the risk verdict that permitted it, in one call. + /// @dev The verdict rides along at no extra transaction cost, so an allowed packet always + /// carries an auditable reason for having been allowed. `action` must be ACTION_ALLOW: + /// a packet that was blocked or held cannot also have been verified. An owner-approved + /// release is reported as ACTION_ALLOW too — a human allowed it — with the reason mask + /// still carrying why it had been held. function submitVerification( bytes calldata packetHeader, bytes32 payloadHash, - uint64 confirmations + uint64 confirmations, + uint8 action, + uint16 score, + uint256 reasonMask, + bytes32 evidenceHash ) external onlyOperator { + if (action != ACTION_ALLOW) revert VerificationRequiresAllow(action); IReceiveUlnE2(receiveUln).verify(packetHeader, payloadHash, confirmations); + emit RiskVerdict(payloadHash, action, score, reasonMask, evidenceHash); + } + + /// @notice Record a verdict for a packet that was NOT verified. + /// @dev Withholding the attestation is what actually stops the packet; this only leaves the + /// audit trail. It is therefore best-effort by design — the worker treats a failure + /// here as a lost record, never as a failure to enforce. + function recordVerdict( + bytes32 payloadHash, + uint8 action, + uint16 score, + uint256 reasonMask, + bytes32 evidenceHash + ) external onlyOperator { + if (action > ACTION_BLOCK) revert UnknownAction(action); + if (action == ACTION_ALLOW) revert AllowNotSeparatelyRecorded(); + emit RiskVerdict(payloadHash, action, score, reasonMask, evidenceHash); + } + + /// @notice Clear a packet the worker withheld for manual review. + /// @dev Emits only; no storage. The worker observes `PacketApproved` and releases the + /// packet from its local deferred queue. Approval is a human override of a risk + /// verdict, so it is separated from the operator key by design — a compromised or + /// buggy worker cannot approve the packets it chose to hold. + function approvePacket(bytes32 payloadHash) external onlyOwner { + emit PacketApproved(payloadHash, msg.sender); } function setOperator(address _operator) external onlyOwner { @@ -66,6 +151,12 @@ contract ComplianceDVN is ILayerZeroDVN, Ownable { emit OperatorSet(_operator); } + function setSendUln(address _sendUln) external onlyOwner { + require(_sendUln != address(0), "zero sendUln"); + sendUln = _sendUln; + emit SendUlnSet(_sendUln); + } + function setReceiveUln(address _receiveUln) external onlyOwner { require(_receiveUln != address(0), "zero receiveUln"); receiveUln = _receiveUln; diff --git a/contracts/MyOFT.sol b/contracts/MyOFT.sol index 89d0574..a06015a 100644 --- a/contracts/MyOFT.sol +++ b/contracts/MyOFT.sol @@ -10,8 +10,14 @@ contract MyOFT is OFT { string memory _symbol, address _lzEndpoint, address _delegate - ) OFT(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) { - // Uncomment the line below to mint test tokens on deployment (for testnet only) - // _mint(msg.sender, 100000 * (10 ** 18)); + ) OFT(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) {} + + /// @notice Open mint, TESTNET ONLY — same as ToyOFT, so the demo tasks work against either. + /// @dev Deliberately unguarded: the demo mints to itself before each send, and a testnet OFT + /// with no way to obtain tokens cannot be used to exercise the DVN at all. Do NOT ship + /// this to a network where the token has value. + /// `virtual` because MyOFTMock declares the same function for the hardhat tests. + function mint(address _to, uint256 _amount) public virtual { + _mint(_to, _amount); } } diff --git a/contracts/mocks/MyOFTMock.sol b/contracts/mocks/MyOFTMock.sol index 3ebb888..18a08f6 100644 --- a/contracts/mocks/MyOFTMock.sol +++ b/contracts/mocks/MyOFTMock.sol @@ -12,7 +12,9 @@ contract MyOFTMock is MyOFT { address _delegate ) MyOFT(_name, _symbol, _lzEndpoint, _delegate) {} - function mint(address _to, uint256 _amount) public { + // Now identical to the inherited MyOFT.mint, kept as an explicit override so the mock's + // intent stays visible at the point tests read it. + function mint(address _to, uint256 _amount) public override { _mint(_to, _amount); } } diff --git a/contracts/mocks/ReceiveUlnMock.sol b/contracts/mocks/ReceiveUlnMock.sol new file mode 100644 index 0000000..0a837c4 --- /dev/null +++ b/contracts/mocks/ReceiveUlnMock.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +/// @notice Minimal stand-in for ReceiveUln302: records what `verify` was called with so tests +/// can assert the DVN forwarded the attestation faithfully. +contract ReceiveUlnMock { + bytes public lastHeader; + bytes32 public lastPayloadHash; + uint64 public lastConfirmations; + uint256 public calls; + + function verify(bytes calldata _header, bytes32 _payloadHash, uint64 _confirmations) external { + lastHeader = _header; + lastPayloadHash = _payloadHash; + lastConfirmations = _confirmations; + calls++; + } +} diff --git a/demo/README.md b/demo/README.md new file mode 100644 index 0000000..d1585c2 --- /dev/null +++ b/demo/README.md @@ -0,0 +1,64 @@ +# demo/ + +데모 전용 자산 모음. 프로덕션 파이프라인(컨트랙트 컴파일, worker/indexer 서비스, 테스트)은 이 폴더 +없이도 완결적으로 동작하며, 여기 있는 것들은 전부 시연을 위한 것이다. + +## 구성 + +| 경로 | 내용 | +| ------------ | --------------------------------------------------------------------------------------------- | +| `dashboard/` | 데모 대시보드 — MetaMask 크로스체인 전송, owner 승인/거절, 제재 목록·판정 로그, 홉 그래프 | +| `contracts/` | 데모 전용 컨트랙트 소스 (`FakeStablecoinMock`, `RiskyProxyMock`) | +| `prebuilt/` | 위 소스의 컴파일 아티팩트 — 메인 파이프라인이 컴파일하지 않으므로 배포 스크립트가 이걸 사용 | +| `deploy/` | 데모 컨트랙트 hardhat-deploy 스크립트 (`FakeUsdcOFT`, `RiskyProxyMock`, `FakeStablecoinMock`) | +| `script/` | `mintDemo.ts` — 데모 지갑 5개에 토큰 100개씩 발행 (idempotent) | + +## 대시보드 실행 + +```bash +cd demo/dashboard +python serve.py # http://localhost:8080 +``` + +배포 주소·체인·라벨은 전부 `dashboard/config.js`에 있다. 페이지는 정적 파일이고, 모든 쓰기는 +MetaMask 서명으로만 이루어진다 — 키가 이 폴더나 백엔드에 들어올 일이 없다. + +## 데모 컨트랙트 배포 + +배포 스크립트는 hardhat 설정의 `paths.deploy`(`['deploy', 'demo/deploy']`)로 발견되므로 평소처럼 +태그로 선택해 실행한다: + +```bash +npx hardhat deploy --network base-sepolia --tags RiskyProxyMock +npx hardhat deploy --network base-sepolia --tags FakeUsdcOFT # MyOFT 아티팩트 사용, 별도 wiring 필요 +``` + +`FakeUsdcOFT`는 `contracts/MyOFT.sol`(메인 트리, 계속 컴파일됨)을 쓰므로 prebuilt가 필요 없다. +`RiskyProxyMock`/`FakeStablecoinMock`은 `prebuilt/`의 아티팩트로 배포된다. + +### prebuilt 재생성 + +`demo/contracts/*.sol`을 수정했다면, 파일을 잠시 `contracts/mocks/`로 복사해 컴파일한 뒤 아티팩트를 +다시 가져온다: + +```bash +cp demo/contracts/RiskyProxyMock.sol contracts/mocks/ +npx hardhat compile +cp artifacts/contracts/mocks/RiskyProxyMock.sol/RiskyProxyMock.json demo/prebuilt/ +rm contracts/mocks/RiskyProxyMock.sol +``` + +## 토큰 발행 + +```bash +npx hardhat run demo/script/mintDemo.ts --network base-sepolia +TOKEN=FakeUsdcOFT npx hardhat run demo/script/mintDemo.ts --network base-sepolia +``` + +발행 대상 지갑 목록은 스크립트 상단의 `DEMO_WALLETS`에 있다. + +## 주의 + +- 여기 있는 토큰은 전부 open-mint 테스트넷 전용이다. 가치가 있는 네트워크에 배포하지 말 것. +- `FakeStablecoinMock`은 초기 시나리오의 잔재로 현재 대시보드는 사용하지 않는다 (수취인 기반 미끼 + → 전송 토큰 기반 `FakeUsdcOFT`로 대체됨). 배포 기록이 있어 보존한다. diff --git a/demo/contracts/FakeStablecoinMock.sol b/demo/contracts/FakeStablecoinMock.sol new file mode 100644 index 0000000..07c8684 --- /dev/null +++ b/demo/contracts/FakeStablecoinMock.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +/// @title FakeStablecoinMock +/// @notice A testnet decoy that claims to be USDC, for exercising the risk engine's +/// impersonation check. It is NOT a stablecoin and holds no value. +/// @dev The engine's token screening resolves a subject's underlying token through `token()`, +/// reads `symbol()`/`decimals()`, and compares the address against the chain's canonical +/// issuer (see `CANONICAL_STABLECOINS` in worker/assess/providers/token.ts). Claiming a +/// watched symbol from a non-canonical address is exactly the pattern +/// `fake_stablecoin_suspect` exists to catch — so this contract asserts the symbol and +/// nothing else. Deploy only to testnets. +contract FakeStablecoinMock is ERC20 { + constructor() ERC20("USD Coin", "USDC") {} + + /// @dev Six, like the real thing: the check is about the address, and matching the decimals + /// keeps the decoy from being dismissed on a detail the engine does not rely on. + function decimals() public pure override returns (uint8) { + return 6; + } + + /// @notice Reports itself as its own underlying token. + /// @dev This is what makes the engine treat the address as a token rather than a plain OApp: + /// `resolveToken` calls `token()` and screens whatever address comes back. + function token() external view returns (address) { + return address(this); + } + + /// @notice Open mint, testnet only — a decoy with no supply is harder to look at in an explorer. + function mint(address _to, uint256 _amount) external { + _mint(_to, _amount); + } +} diff --git a/demo/contracts/RiskyProxyMock.sol b/demo/contracts/RiskyProxyMock.sol new file mode 100644 index 0000000..9a500f9 --- /dev/null +++ b/demo/contracts/RiskyProxyMock.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +/// @title RiskyProxyMock +/// @notice A testnet decoy that looks like an upgradeable proxy controlled by a flagged address, +/// for exercising the risk engine's `contract_admin_risk` check. +/// @dev The engine reads the two EIP-1967 slots directly (see `assess/providers/contract.ts`): +/// an implementation slot that is set means the code behind this address can change, and the +/// admin slot names whoever can change it. It then looks that admin up in the risk store — +/// a flagged admin is the signal, because today's clean code says nothing about tomorrow's +/// if a sanctioned party can swap it out. +/// +/// The slots are written straight to storage rather than by deploying a real proxy: what is +/// being demonstrated is the engine's reading of them, and a forwarding proxy would add a +/// delegatecall path with nothing to delegate to. +contract RiskyProxyMock { + /// @dev keccak256("eip1967.proxy.implementation") - 1 + bytes32 private constant SLOT_IMPLEMENTATION = + 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + /// @dev keccak256("eip1967.proxy.admin") - 1 + bytes32 private constant SLOT_ADMIN = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + + /// @param _admin The address to present as able to upgrade this contract. Point it at an + /// address the operator has flagged (e.g. one in TEST_DENYLIST) for the check to fire. + /// @param _implementation Any non-zero address; its only job is to make the proxy slot set. + constructor(address _admin, address _implementation) { + require(_admin != address(0), "zero admin"); + require(_implementation != address(0), "zero implementation"); + assembly { + sstore(SLOT_ADMIN, _admin) + sstore(SLOT_IMPLEMENTATION, _implementation) + } + } + + /// @notice The admin as stored in the EIP-1967 slot, for anyone reading it the easy way. + function admin() external view returns (address a) { + assembly { + a := sload(SLOT_ADMIN) + } + } + + /// @notice The implementation as stored in the EIP-1967 slot. + function implementation() external view returns (address i) { + assembly { + i := sload(SLOT_IMPLEMENTATION) + } + } +} diff --git a/demo/dashboard/README.md b/demo/dashboard/README.md new file mode 100644 index 0000000..7d23896 --- /dev/null +++ b/demo/dashboard/README.md @@ -0,0 +1,62 @@ +# Demo dashboard + +Four static pages over the read-only APIs of the worker and indexer. No build step, no framework, +no server-side state — every write is signed in MetaMask, so this page never holds a key. + +```bash +cd dashboard && python serve.py +``` + +Then open . Use [`serve.py`](serve.py) rather than `python -m http.server`: +the latter answers conditional requests with 304, so an edited page keeps serving the old bytes +until a hard reload — which during a demo reads as "the change did not work". + +## Wallets + +Connecting enumerates every injected provider (EIP-6963, plus the legacy `window.ethereum` +array) and prefers MetaMask, then tries the rest in turn. A multi-chain wallet that claims +`window.ethereum` without holding an Ethereum account rejects `eth_requestAccounts` with +*"Unable to find any account for 60"* — 60 being Ethereum's BIP-44 coin type — so taking +`window.ethereum` on faith dead-ends the page even when MetaMask is installed alongside. The +connected wallet's name is shown next to the address. + +The interface is in Korean. On-chain vocabulary (`sanctions_1hop`, `block`, …) is shown in Korean +but keeps its identifier on the element's `title`, because that string is what appears in the event +log, the policy source, and the CLI — an auditor comparing this screen against a transaction needs +it within reach. Panel rationale sits behind a `?` marker rather than in the layout. + +| Page | What it does | +| --- | --- | +| `index.html` (전송) | Cross-chain OFT send (MetaMask), testUSDT faucet, same-chain transfer, and **내 패킷** — the tracker that makes a withheld packet visible | +| `holds.html` (보류 패킷) | The worker's held-packet queue, with owner-signed `approvePacket`, plus the on-chain approval trail | +| `overview.html` (종합 현황) | Verdict log with decoded reason masks, sanctions seed list, watched config, the served feed, live proximity labels, recent edges | +| `graph.html` (그래프) | Force-directed transfer graph coloured by distance to a sanctioned seed, with per-address inspection | + +## Font + +Pretendard Variable is self-hosted in [`fonts/`](fonts/) — one face covering Hangul and Latin, so +mixed strings like `피드 v120` no longer fall back per-glyph into two mismatched weights. Licensed +under SIL OFL 1.1; the licence ships alongside it in `fonts/LICENSE-Pretendard.txt`, as the licence +requires. Addresses and hashes keep the system monospace stack, since hex needs fixed advance. + +## Configuration + +Edit [`config.js`](config.js) — chain metadata, contract addresses, API origins, and the friendly +names shown instead of raw hex. It is a plain file on purpose: it holds no secrets, and a wrong +value should be visible at a glance rather than buried in a bundle. + +After redeploying contracts, update `token` and `dvn` per chain there, exactly as you would in the +three `.env` files. + +## What it reads + +- **indexer** `:9091` — `/api/status`, `/api/seeds`, `/api/verdicts`, `/api/approvals`, `/api/edges`, + `/api/proximity`, `/feed/latest.json` +- **worker** `:9090` — `/status`, `/pending` + +Both send `access-control-allow-origin: *`; everything they expose is already public (on-chain +events, published sanctions lists, a signed feed). Neither exposes a write endpoint: releasing a +held packet is an on-chain owner action, never an HTTP call. + +If the worker is not running, the pages say so rather than showing an empty queue — "no holds" and +"cannot tell" must not look the same. diff --git a/demo/dashboard/config.js b/demo/dashboard/config.js new file mode 100644 index 0000000..9bd1727 --- /dev/null +++ b/demo/dashboard/config.js @@ -0,0 +1,92 @@ +/** + * Demo dashboard configuration. + * + * Deliberately a plain file the operator edits by hand: this page is served statically, holds no + * secrets, and every write it performs is signed in MetaMask. Nothing here is a credential — + * addresses and RPC URLs only. If a value is wrong the page says so rather than guessing. + */ +window.DVN_CONFIG = { + // Read-only APIs. The worker one is optional: without it the Held Packets page explains that + // the worker is not reachable instead of showing an empty queue, which would look like "no holds". + indexerApi: 'http://localhost:9091', + workerApi: 'http://localhost:9090', + + chains: { + baseSepolia: { + label: 'Base Sepolia', + eid: 40245, + chainId: 84532, + chainIdHex: '0x14a34', + rpc: 'https://sepolia.base.org', + explorer: 'https://sepolia.basescan.org', + dvn: '0x497E0962BeD72DC12Fb249995cA618a929C0d17A', + nativeLabel: 'ETH', + // Same address on both chains; needed to read channel nonces and to skip a stalled one. + endpoint: '0x6EDCE65403992e310A62460808c4b910D972f10f', + }, + optimismSepolia: { + label: 'OP Sepolia', + eid: 40232, + chainId: 11155420, + chainIdHex: '0xaa37dc', + rpc: 'https://sepolia.optimism.io', + explorer: 'https://sepolia-optimism.etherscan.io', + dvn: '0x7843CAf643A175fc3d0E4746678BEdAAFc396e65', + nativeLabel: 'ETH', + endpoint: '0x6EDCE65403992e310A62460808c4b910D972f10f', + }, + }, + + /** + * Sendable tokens, each wired to the DVN on both chains. + * + * The impersonation check runs on the token being MOVED — the engine resolves a packet's OApp + * through `token()` and compares it against the chain's canonical issuer. So demonstrating it + * means sending the decoy, not sending to it. + */ + tokens: { + // On-chain symbol is `testUSDT`; the label is the Korean reading of the same thing. + testUSDT: { + label: '테스트 USDT', + addresses: { + baseSepolia: '0x2DC5e5177a172c0FDc7c7d490A5D6D098e822eB7', + optimismSepolia: '0x5237Ca5731f00741E10E5dB0cedA0796e21f08a6', + }, + }, + // Claims the USDC symbol from an address that is not Circle's — which is what + // `fake_stablecoin_suspect` looks for. + fakeUSDC: { + label: '가짜 USDC (미끼)', + addresses: { + baseSepolia: '0xcE65144C75d77c479b7FF12Cff41a1AC5359A578', + optimismSepolia: '0xf970027c806420a0222F5b72b097d3fDeC81b228', + }, + }, + }, + + /** Named addresses, shown as chips instead of raw hex wherever they appear. */ + labels: { + '0x8583894d0e57e42abb83039537f314490038efa0': 'owner (O)', + '0x01d24ae2cd8ad18472bd00afe4ec425e800e184d': 'worker (W)', + '0xcd346e8762e27d0558a260c1c3562127c52ad45b': 'feed (F)', + '0x25d10657a2642fe8cd6bee501dbd0939d79bd90f': '정상 지갑 (A)', + '0x9a1c282eba5e9a97290cac530902fb00dcf2ece2': '1홉 지갑 (B)', + '0x000000000000000000000000000000000000dead': '차단 목록 (S)', + '0x0330070fd38ec3bb94f58fa55d40368271e9e54a': 'OFAC 시드 (X)', + '0xce65144c75d77c479b7ff12cff41a1ac5359a578': '가짜 USDC OFT (Base)', + '0xf970027c806420a0222f5b72b097d3fdec81b228': '가짜 USDC OFT (OP)', + '0x9771013d82dcc2bdb489b982b4f201fd698a15e6': '위험 프록시 (OP)', + '0xebb646c8ed3a06d37bd779c994a9d479abc788d3': '위험 프록시 (Base)', + }, + + /** + * Contract whose EIP-1967 admin is a flagged address, per chain. + * + * Screened as the OFT RECIPIENT — the only demo where what matters is a property of the contract + * receiving the funds rather than the sender's history or the token's identity. + */ + riskyProxy: { + baseSepolia: '0xeBb646C8eD3A06d37bd779C994A9d479abC788d3', + optimismSepolia: '0x9771013D82dcC2bdb489B982B4f201FD698A15e6', + }, +} diff --git a/demo/dashboard/fonts/LICENSE-Pretendard.txt b/demo/dashboard/fonts/LICENSE-Pretendard.txt new file mode 100644 index 0000000..497b88f --- /dev/null +++ b/demo/dashboard/fonts/LICENSE-Pretendard.txt @@ -0,0 +1,94 @@ +Copyright (c) 2021, Kil Hyung-jin (https://github.com/orioncactus/pretendard), +with Reserved Font Name Pretendard. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/demo/dashboard/fonts/PretendardVariable.woff2 b/demo/dashboard/fonts/PretendardVariable.woff2 new file mode 100644 index 0000000..49c54b5 Binary files /dev/null and b/demo/dashboard/fonts/PretendardVariable.woff2 differ diff --git a/demo/dashboard/graph.html b/demo/dashboard/graph.html new file mode 100644 index 0000000..7bb162c --- /dev/null +++ b/demo/dashboard/graph.html @@ -0,0 +1,341 @@ + + + + + +Compliance DVN — 그래프 + + + +
+

Compliance DVN

+ +
+
+ +
+
+
+

전송 그래프 & 제재 근접성

+ + + + +
+ + +
+ 제재 시드 + 1홉 + 2홉 + 3홉 + 노출 없음 + 데모 지갑 + 빨간 엣지 = 시드에 직접 도달한 전송 + 점선 = 크로스체인 전송 +
+
+ +
+
+

선택한 주소

+

노드를 클릭하면 상세 정보가 표시됩니다.

+
+
+

라벨이 붙은 주소

+
+
+
+
+ + + + + + + diff --git a/demo/dashboard/holds.html b/demo/dashboard/holds.html new file mode 100644 index 0000000..162a890 --- /dev/null +++ b/demo/dashboard/holds.html @@ -0,0 +1,466 @@ + + + + + +Compliance DVN — 보류 패킷 + + + +
+

Compliance DVN

+ +
+
+ +
+
+
+

보류 패킷 — owner 검토

+ + + +
+
+
+
+
+ +
+

메시지 채널 상태

+
+

실행 대기 중인 nonce

+
+
+
+ +
+

온체인에 기록된 승인 내역

+
+
+ +
+

수동 승인

+
+
+ + +
+
+ + +
+
+
+ +
+
+
+
+ + + + + + + diff --git a/demo/dashboard/index.html b/demo/dashboard/index.html new file mode 100644 index 0000000..6bff121 --- /dev/null +++ b/demo/dashboard/index.html @@ -0,0 +1,587 @@ + + + + + +Compliance DVN — 전송 + + + +
+

Compliance DVN

+ +
+
+ +
+
+ +
+

크로스체인 전송 (OFT)

+ + +

지갑

+
+ +
+ +

전송 내용

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + +
+ + + + + +
+ +
+ + +
+
+
+ + +
+

토큰 도구

+ +

토큰 발행 (Faucet)

+
+ + +
+ +

같은 체인 내 전송

+ + +
+ + +
+
+ + +
+
+
+
+ + +
+
+

내 패킷

+ + + + +
+
+
+
+ + + + + + + diff --git a/demo/dashboard/lib.js b/demo/dashboard/lib.js new file mode 100644 index 0000000..48e08cf --- /dev/null +++ b/demo/dashboard/lib.js @@ -0,0 +1,741 @@ +/* Shared helpers: chain/wallet plumbing, API access, formatting, reason-code decoding. */ + +const CFG = window.DVN_CONFIG +const CHAINS = CFG.chains + +/** + * Reason bit -> code. MUST mirror worker/assess/verdict.ts REASON_BITS, which is append-only — + * so this table only ever grows, and an unknown bit renders as `bit:N` rather than vanishing. + */ +const REASON_BITS = { + 0: 'sanctions', + 1: 'sanctioned_mixer', + 2: 'scam_token', + 3: 'operator_deny', + 4: 'sanctions_1hop', + 5: 'sanctions_1hop_inbound', + 6: 'mixer_exposure', + 7: 'fake_stablecoin_suspect', + 8: 'honeypot_suspect', + 9: 'contract_admin_risk', + 10: 'unverified_contract', + 11: 'upgradeable_proxy', + 12: 'contract_check_unavailable', + 13: 'token_check_unavailable', + 14: 'owner_approved', + 15: 'sanctions_2hop', + 16: 'sanctions_3hop', + 17: 'sanctions_2hop_inbound', + 18: 'sanctions_3hop_inbound', + 19: 'mixer_exposure_2hop', + 20: 'mixer_exposure_3hop', + 255: 'unmapped', +} + +const ACTIONS = ['allow', 'delay', 'manual-review', 'block'] + +const TOKENS = CFG.tokens + +/** Address of a configured token on one chain. */ +const tokenAt = (tokenKey, chainKey) => TOKENS[tokenKey].addresses[chainKey] + +/** + * Korean readings for the on-chain vocabulary. + * + * Display only — the identifier stays on the element's `title`, because it is what appears in the + * event log, the policy source, and the CLI. An auditor comparing this screen against a + * transaction needs the original string within reach. + */ +const LABEL_KO = { + sanctions: '제재 대상', + sanctioned_mixer: '제재 믹서', + scam_token: '스캠 토큰', + operator_deny: '운영자 차단', + sanctions_1hop: '제재 1홉', + sanctions_2hop: '제재 2홉', + sanctions_3hop: '제재 3홉', + sanctions_1hop_inbound: '제재 유입 1홉', + sanctions_2hop_inbound: '제재 유입 2홉', + sanctions_3hop_inbound: '제재 유입 3홉', + mixer_exposure: '믹서 노출', + mixer_exposure_2hop: '믹서 노출 2홉', + mixer_exposure_3hop: '믹서 노출 3홉', + fake_stablecoin_suspect: '가짜 스테이블코인 의심', + honeypot_suspect: '허니팟 의심', + contract_admin_risk: '컨트랙트 관리자 위험', + unverified_contract: '미검증 컨트랙트', + upgradeable_proxy: '업그레이드 가능 프록시', + contract_check_unavailable: '컨트랙트 조회 실패', + token_check_unavailable: '토큰 조회 실패', + owner_approved: 'owner 승인', + unmapped: '미매핑 사유', +} + +const ACTION_KO = { allow: '통과', delay: '지연', 'manual-review': '수동 검토', block: '차단' } + +const labelKo = (code) => LABEL_KO[code] ?? code + +/** Decode a uint256 reason mask (decimal string) back to labels. */ +function decodeReasons(mask) { + let m + try { + m = BigInt(String(mask ?? '0')) + } catch { + return [] + } + const out = [] + for (let bit = 0n; bit <= 255n; bit++) { + if ((m >> bit) & 1n) out.push(REASON_BITS[Number(bit)] ?? `bit:${bit}`) + } + return out +} + +const short = (a) => (a && a.length > 14 ? `${a.slice(0, 8)}…${a.slice(-6)}` : a ?? '') +const labelOf = (a) => (a ? CFG.labels[a.toLowerCase()] : undefined) + +/** + * Address chip: the friendly name when we know it, always with the full value on hover. + * + * Only the hex form gets the monospace face. A name rendered in it turned "owner (O)" into what + * looked like "owner (0)" — in a monospace stack the capital O and the digit sit at the same width + * with nearly the same shape. Fixed advance is for comparing hex, not for reading words. + */ +function addrChip(a, { link, chain } = {}) { + if (!a) return '—' + const name = labelOf(a) + const inner = name + ? `${name}` + : `${short(a)}` + const base = chain && CHAINS[chain] ? `${CHAINS[chain].explorer}/address/${a}` : undefined + const href = link === false ? undefined : base + return href ? `${inner}` : inner +} + +/** + * Copy button for a value the row only shows abbreviated. + * + * The click is handled by one delegated listener rather than a handler per button: these render + * inside tables that are replaced wholesale on every refresh, and per-row handlers would be rebound + * hundreds of times a minute. + */ +function copyBtn(value, label = '주소 복사') { + return `` +} + +/** Selection-based copy, for when the async clipboard is unavailable or refuses. */ +function copyViaSelection(value) { + const ta = document.createElement('textarea') + ta.value = value + ta.style.cssText = 'position:fixed;top:0;left:0;opacity:0' + document.body.appendChild(ta) + ta.select() + const ok = document.execCommand('copy') + ta.remove() + return ok +} + +document.addEventListener('click', async (e) => { + const btn = e.target.closest?.('[data-copy]') + if (!btn) return + const value = btn.dataset.copy + + // The async clipboard rejects when the document is not focused, so a refusal is a reason to fall + // back rather than to give up — treating "API exists" as "API will work" loses the copy silently. + let ok = false + try { + await navigator.clipboard.writeText(value) + ok = true + } catch { + try { + ok = copyViaSelection(value) + } catch { + ok = false + } + } + + btn.classList.add(ok ? 'copied' : 'failed') + btn.title = ok ? '복사됨' : '복사 실패 — 주소를 직접 선택해 복사하세요' + setTimeout(() => { + btn.classList.remove('copied', 'failed') + btn.title = '주소 복사' + }, 1400) +}) + +function txLink(chain, hash) { + const c = CHAINS[chain] + if (!c || !hash) return short(hash) + return `${short(hash)}` +} + +const LZ_SCAN = 'https://testnet.layerzeroscan.com' + +/** + * Link into LayerZero Scan by SOURCE transaction. + * + * It has to be the source tx: that is the one carrying the message, so Scan can show the packet's + * lifecycle — including a blocked packet sitting unverified forever, which is the whole point of + * looking. The verdict transaction is a call to our own DVN and is not part of any message, so + * Scan would have nothing to show for it. + */ +function lzLink(srcTxHash, text = 'LayerZero Scan') { + if (!srcTxHash) return '' + return `${text}` +} + +/** Sends remembered by this browser. Shared so any page can resolve a payload back to its source tx. */ +const SENT_KEY = 'dvn-demo-sent' +const loadSent = () => { + try { + return JSON.parse(localStorage.getItem(SENT_KEY) ?? '[]') + } catch { + return [] + } +} +const saveSent = (list) => localStorage.setItem(SENT_KEY, JSON.stringify(list.slice(0, 40))) + +/** + * payloadHash -> the send that produced it. + * + * The indexer records a verdict's own transaction, not the send it judged, so the link back to the + * source tx is only knowable here — from what this browser sent. Rows it does not recognise simply + * show no LayerZero link rather than a guess. + */ +function sentByPayload() { + const map = new Map() + for (const s of loadSent()) if (s.payloadHash) map.set(s.payloadHash.toLowerCase(), s) + return map +} + +/** + * `${srcKey}>${dstKey}/${tokenKey}:${nonce}` -> the send that occupied that channel slot. + * + * Keyed by token as well as pathway: each token is its own channel with its own nonce sequence, so + * nonce 2 exists once per token and the two must not be confused for each other. + * + * Nothing on-chain ties a nonce to a business meaning — the channel only knows the ordering. So a + * stalled nonce can only be named from what this browser sent; anything else is honestly unknown. + */ +function sentByNonce() { + const map = new Map() + for (const s of loadSent()) { + if (s.nonce !== undefined) map.set(`${s.srcKey}>${s.dstKey}/${s.tokenKey ?? 'testUSDT'}:${s.nonce}`, s) + } + return map +} + +function fmtTime(unixSeconds) { + if (!unixSeconds) return '—' + return new Date(Number(unixSeconds) * 1000).toLocaleString() +} + +function fmtAgo(ms) { + const s = Math.floor(ms / 1000) + if (s < 60) return `${s}초 전` + if (s < 3600) return `${Math.floor(s / 60)}분 전` + return `${Math.floor(s / 3600)}시간 전` +} + +/** 18-decimal amount -> trimmed decimal string, without pulling in a bignum library. */ +function fmtUnits(raw, decimals = 18) { + const s = String(raw ?? '0').padStart(decimals + 1, '0') + const whole = s.slice(0, -decimals).replace(/^0+(?=\d)/, '') + const frac = s.slice(-decimals).replace(/0+$/, '') + return frac ? `${whole}.${frac.slice(0, 6)}` : whole +} + +function actionBadge(action) { + const name = typeof action === 'number' ? ACTIONS[action] ?? `action:${action}` : action + const cls = { allow: 'ok', delay: 'warn', 'manual-review': 'hold', block: 'bad' }[name] ?? 'muted' + return `${ACTION_KO[name] ?? name}` +} + +/** Labels that assert the subject IS the thing, rather than that it is near one — shown in red. */ +const DIRECT_HIT = new Set(['sanctions', 'sanctioned_mixer', 'scam_token', 'operator_deny']) + +function reasonChips(codes) { + if (!codes || !codes.length) return '' + return codes + .map((c) => `${labelKo(c)}`) + .join(' ') +} + +/** + * A `?` marker that reveals an explanation card on hover. + * + * The design rationale behind a panel matters when someone asks "why can't the worker just release + * this?", and not at all the rest of the time. Kept out of the layout until asked for, so the + * panel itself stays a list of facts. + */ +function helpMark(html) { + return `${html}` +} + +/* ── API ─────────────────────────────────────────────────────────────────── */ + +async function api(base, path) { + const res = await fetch(`${base}${path}`, { cache: 'no-store' }) + if (!res.ok) throw new Error(`${path} -> HTTP ${res.status}`) + return res.json() +} + +const indexerApi = (path) => api(CFG.indexerApi, path) +const workerApi = (path) => api(CFG.workerApi, path) + +/* ── wallet ──────────────────────────────────────────────────────────────── */ + +const ERC20_ABI = [ + 'function balanceOf(address) view returns (uint256)', + 'function transfer(address to, uint256 amount) returns (bool)', + 'function mint(address to, uint256 amount)', + 'function decimals() view returns (uint8)', + 'function symbol() view returns (string)', +] + +const OFT_ABI = [ + 'function quoteSend((uint32 dstEid, bytes32 to, uint256 amountLD, uint256 minAmountLD, bytes extraOptions, bytes composeMsg, bytes oftCmd) sendParam, bool payInLzToken) view returns ((uint256 nativeFee, uint256 lzTokenFee))', + 'function send((uint32 dstEid, bytes32 to, uint256 amountLD, uint256 minAmountLD, bytes extraOptions, bytes composeMsg, bytes oftCmd) sendParam, (uint256 nativeFee, uint256 lzTokenFee) fee, address refundAddress) payable returns ((bytes32 guid, uint64 nonce, (uint256 nativeFee, uint256 lzTokenFee) fee), (uint256 amountSentLD, uint256 amountReceivedLD))', +] + +const DVN_ABI = ['function approvePacket(bytes32 payloadHash)', 'function owner() view returns (address)'] + +/** + * Endpoint reads for the message channel, plus `skip`. + * + * A vetoed packet is never verified, so its nonce never gets a payload hash — and the channel + * clears nonces strictly in order. Every later message on that pathway is therefore stuck behind + * it, permanently. `skip` is LayerZero's way out: the OApp or its delegate advances past a nonce + * that will never arrive. It is an administrative decision about a specific message, which is why + * it belongs to the owner rather than to the worker. + */ +const ENDPOINT_ABI = [ + 'function inboundNonce(address receiver, uint32 srcEid, bytes32 sender) view returns (uint64)', + 'function lazyInboundNonce(address receiver, uint32 srcEid, bytes32 sender) view returns (uint64)', + 'function inboundPayloadHash(address receiver, uint32 srcEid, bytes32 sender, uint64 nonce) view returns (bytes32)', + 'function outboundNonce(address sender, uint32 dstEid, bytes32 receiver) view returns (uint64)', + 'function skip(address oapp, uint32 srcEid, bytes32 sender, uint64 nonce)', +] + +const HASH_ZERO = '0x' + '0'.repeat(64) + +/** + * State of one direction's channel: how far it has executed, and whether it is stalled. + * + * "Stalled" means the source has sent a message at the next nonce but that nonce holds no payload + * hash — the packet the DVN refused. Without comparing against the source's outbound nonce a gap + * would be indistinguishable from a packet that simply has not been committed yet. + */ +async function channelState(srcKey, dstKey, tokenKey = 'testUSDT') { + const src = CHAINS[srcKey] + const dst = CHAINS[dstKey] + // A channel is keyed by (receiver OApp, srcEid, sender OApp), so each token has its own. + const srcOApp = tokenAt(tokenKey, srcKey) + const dstOApp = tokenAt(tokenKey, dstKey) + const senderB32 = ethers.utils.hexZeroPad(srcOApp, 32) + const receiverB32 = ethers.utils.hexZeroPad(dstOApp, 32) + + const dstEp = new ethers.Contract(dst.endpoint, ENDPOINT_ABI, readProvider(dstKey)) + const srcEp = new ethers.Contract(src.endpoint, ENDPOINT_ABI, readProvider(srcKey)) + + const [executed, verified, sent] = await Promise.all([ + dstEp.lazyInboundNonce(dstOApp, src.eid, senderB32), + dstEp.inboundNonce(dstOApp, src.eid, senderB32), + srcEp.outboundNonce(srcOApp, dst.eid, receiverB32), + ]) + + const next = verified.add(1) + const nextHash = next.lte(sent) ? await dstEp.inboundPayloadHash(dstOApp, src.eid, senderB32, next) : HASH_ZERO + const stalled = next.lte(sent) && nextHash === HASH_ZERO + + // Per-nonce detail for everything not yet executed, so a stall can be explained rather than just + // reported. Capped: each entry costs one call, and only the unexecuted tail is interesting. + const from = Math.max(1, executed.toNumber() + 1) + const to = sent.toNumber() + const first = Math.max(from, to - 11) + const slots = [] + for (let n = first; n <= to; n++) { + const hash = await dstEp.inboundPayloadHash(dstOApp, src.eid, senderB32, n) + slots.push({ + nonce: n, + committed: hash !== HASH_ZERO, + // The gap that holds the line is the first uncommitted nonce; later gaps are behind it. + blocking: hash === HASH_ZERO && n === next.toNumber(), + }) + } + + return { + srcKey, + dstKey, + tokenKey, + executed: executed.toNumber(), + verified: verified.toNumber(), + sent: sent.toNumber(), + stalledAt: stalled ? next.toNumber() : undefined, + pending: Math.max(0, sent.toNumber() - executed.toNumber()), + truncated: from < first, + slots, + } +} + +/** + * Decode the LayerZero V2 81-byte packet header. + * + * A held packet persists its header, and the header already names everything a rejection needs — + * which channel, which slot. Reading it beats looking the pathway up in config: it is the packet's + * own account of itself, and it stays right for a token the dashboard has never heard of. + */ +function decodePacketHeader(headerHex) { + const h = headerHex.startsWith('0x') ? headerHex.slice(2) : headerHex + if (h.length !== 81 * 2) throw new Error(`패킷 헤더는 81바이트여야 합니다 (받은 값: ${h.length / 2}바이트)`) + const at = (startByte, lenBytes) => h.slice(startByte * 2, (startByte + lenBytes) * 2) + return { + nonce: Number(BigInt('0x' + at(1, 8))), + srcEid: parseInt(at(9, 4), 16), + sender: '0x' + at(13, 32), + dstEid: parseInt(at(45, 4), 16), + receiver: '0x' + at(49 + 12, 20), + } +} + +/** + * Is this packet's nonce the one the channel will clear next? + * + * `skip` only works at the head of the queue (`nonce == inboundNonce + 1`) — LayerZero clears + * nonces strictly in order, so a later slot cannot be abandoned before an earlier one. Reading the + * answer lets the page say which packet has to go first instead of surfacing `LZ_InvalidNonce`. + */ +async function skipEligibility(dstKey, header) { + const h = decodePacketHeader(header) + const ep = new ethers.Contract(CHAINS[dstKey].endpoint, ENDPOINT_ABI, readProvider(dstKey)) + const next = (await ep.inboundNonce(h.receiver, h.srcEid, h.sender)).add(1).toNumber() + return { ok: next === h.nonce, next, nonce: h.nonce } +} + +/** + * Reject a packet: skip its nonce so the endpoint can never execute it. + * + * This is the owner's refusal, and it is the endpoint that enforces it — not the DVN and not the + * worker, which holds only the operator key. The worker notices the skip on its next pass and drops + * the packet from its hold queue. + */ +async function skipPacket(dstKey, header) { + const h = decodePacketHeader(header) + await ensureChain(dstKey) + const ep = new ethers.Contract(CHAINS[dstKey].endpoint, ENDPOINT_ABI, signer()) + const tx = await ep.skip(h.receiver, h.srcEid, h.sender, h.nonce) + await tx.wait() + return tx.hash +} + +/** Skip one nonce that will never be verified. Signed by the owner (the OApp's delegate). */ +async function skipNonce(srcKey, dstKey, nonce, tokenKey = 'testUSDT') { + const src = CHAINS[srcKey] + const dst = CHAINS[dstKey] + await ensureChain(dstKey) + const ep = new ethers.Contract(dst.endpoint, ENDPOINT_ABI, signer()) + const tx = await ep.skip(tokenAt(tokenKey, dstKey), src.eid, ethers.utils.hexZeroPad(tokenAt(tokenKey, srcKey), 32), nonce) + await tx.wait() + return tx.hash +} + +let _signer +/** The injected provider actually in use — not necessarily `window.ethereum`. See pickWallet. */ +let _injected +let _walletName = '' + +/** + * Wallets that announced themselves under EIP-6963, keyed by rdns. + * + * The listener is registered before the request is dispatched because wallets answer the request + * event synchronously; a listener added afterwards would miss every announcement. + */ +const _announced = new Map() +window.addEventListener('eip6963:announceProvider', (e) => { + const d = e.detail + if (d && d.info && d.provider) _announced.set(d.info.rdns || d.info.name, d) +}) +window.dispatchEvent(new Event('eip6963:requestProvider')) + +/** + * Every injected EVM provider we can see, best candidate first. + * + * `window.ethereum` alone is not enough. A multi-chain wallet claims it too, and one holding no + * Ethereum account rejects `eth_requestAccounts` with "Unable to find any account for 60" — 60 + * being Ethereum's BIP-44 coin type. EIP-6963 lets us enumerate what is really installed; the + * legacy `providers` array covers wallets that predate it. + */ +/** Name a pre-EIP-6963 provider from its own flag, so an error can say which wallet refused. */ +function legacyWalletName(p) { + if (p.isMetaMask) return 'MetaMask' + if (p.isPhantom) return 'Phantom' + if (p.isRabby) return 'Rabby' + if (p.isCoinbaseWallet) return 'Coinbase Wallet' + if (p.isBraveWallet) return 'Brave Wallet' + if (p.isTrust || p.isTrustWallet) return 'Trust Wallet' + if (p.isOkxWallet || p.isOKExWallet) return 'OKX Wallet' + return '주입된 지갑' +} + +function injectedWallets() { + const found = [] + for (const { info, provider } of _announced.values()) found.push({ name: info.name, rdns: info.rdns, provider }) + + const eth = window.ethereum + if (eth) { + const legacy = Array.isArray(eth.providers) ? eth.providers : [eth] + for (const p of legacy) { + if (found.some((f) => f.provider === p)) continue + found.push({ name: legacyWalletName(p), rdns: '', provider: p }) + } + } + + // MetaMask first: it is what the demo is set up against, and it always has an EVM account. + const rank = (w) => (/metamask/i.test(w.rdns + w.name) ? 0 : /rabby|coinbase|rainbow|trust/i.test(w.rdns + w.name) ? 1 : 2) + return found.sort((a, b) => rank(a) - rank(b)) +} + +function hasWallet() { + return injectedWallets().length > 0 +} + +/** + * Custom-error selectors worth naming, since none of them carry a revert string. + * + * A wallet reports these as "Internal JSON-RPC error", which says nothing about what to change. + * The selector is the only part of the payload that identifies the cause, so it is decoded here. + */ +const REVERT_SELECTORS = { + '0x6592671c': { text: 'lzReceive 실행자 옵션이 유효하지 않습니다 (extraOptions 누락 또는 형식 오류)' }, + '0xf6ff4fb7': { text: '목적지 체인의 peer가 설정되지 않았습니다 — lz:oapp:wire 를 다시 실행하세요' }, + // Not a shortfall: OAppSender._payNative demands msg.value EXACTLY equal to the quoted fee, so + // paying over reverts here too. The argument is the msg.value that was rejected. + '0x9f704120': { + text: 'msg.value가 견적 수수료와 정확히 일치하지 않습니다 (초과 지불도 거부됩니다)', + args: ['uint256'], + render: ([v]) => `보낸 값 ${fmtUnits(v.toString())} ETH`, + }, + '0x71c4efed': { text: '수령 수량이 minAmountLD 아래로 떨어졌습니다' }, + '0xe450d38c': { + text: 'testUSDT 잔액이 부족합니다', + args: ['address', 'uint256', 'uint256'], + render: ([, bal, need]) => `보유 ${fmtUnits(bal.toString())} / 필요 ${fmtUnits(need.toString())}`, + }, + '0x96c6fd1e': { text: '보낸 주소가 유효하지 않습니다' }, + '0x6c1ccdb5': { text: '이 경로의 기본 SendLibrary가 설정되지 않았습니다' }, + '0xf0c10d04': { text: '지원되지 않는 목적지 EID입니다' }, + '0xb5863604': { text: 'OApp delegate 설정이 잘못되었습니다' }, + '0x91ac5e4f': { text: 'Endpoint만 호출할 수 있는 함수입니다' }, + '0xcf479181': { text: '네이티브 잔액이 부족합니다' }, +} + +/** + * Dig the real cause out of a wallet error. + * + * MetaMask wraps a node's response and surfaces only "Internal JSON-RPC error" (-32603); the revert + * payload sits several levels down under a different key depending on the wallet and the ethers + * version. Every place it is known to hide is checked, and the four-byte selector is translated. + */ +function decodeRpcError(err) { + const seen = new Set() + const hexes = [] + const walk = (o, depth) => { + if (!o || depth > 6 || typeof o !== 'object' || seen.has(o)) return + seen.add(o) + for (const v of Object.values(o)) { + if (typeof v === 'string') { + const m = v.match(/0x[0-9a-fA-F]{8,}/) + if (m) hexes.push(m[0]) + } else if (typeof v === 'object') { + walk(v, depth + 1) + } + } + } + walk(err, 0) + + for (const hex of hexes) { + const selector = hex.slice(0, 10).toLowerCase() + const known = REVERT_SELECTORS[selector] + if (!known) continue + let detail = '' + if (known.args) { + // The arguments are where the useful numbers are — what was sent versus what was required. + try { + const decoded = ethers.utils.defaultAbiCoder.decode(known.args, '0x' + hex.slice(10)) + detail = ` — ${known.render(decoded)}` + } catch { + // A truncated payload still identifies the cause; the numbers are a bonus. + } + } + return `${known.text}${detail} (${selector})` + } + const msg = err?.error?.message ?? err?.data?.message ?? err?.reason ?? err?.message ?? String(err) + if (/insufficient funds/i.test(msg)) return '가스비로 쓸 네이티브 잔액이 부족합니다' + return msg +} + +/** Turn a wallet's own rejection into something an operator can act on. */ +function walletError(err, name) { + const msg = String(err && err.message ? err.message : err) + if (/account for 60|no.*(evm|ethereum).*account/i.test(msg)) { + return new Error( + `${name}에 이더리움 계정이 없습니다 (coin type 60). 이 지갑은 EVM 계정 없이 window.ethereum을 ` + + '차지하고 있습니다 — MetaMask를 설치·활성화하거나, 해당 지갑에서 이더리움 계정을 추가하세요.', + ) + } + if (err && err.code === 4001) return new Error('지갑에서 연결을 거부했습니다.') + return err +} + +/** + * Connect a wallet. Tries each injected provider in turn, so one without an EVM account does not + * dead-end the page when a usable wallet is also installed. + */ +async function connect() { + const wallets = injectedWallets() + if (!wallets.length) throw new Error('지갑 확장 프로그램을 찾을 수 없습니다 — MetaMask를 설치하거나 CLI를 사용하세요.') + + let lastErr + for (const w of wallets) { + try { + const provider = new ethers.providers.Web3Provider(w.provider, 'any') + await provider.send('eth_requestAccounts', []) + _injected = w.provider + _walletName = w.name + _signer = provider.getSigner() + return { address: await _signer.getAddress(), provider, wallet: w.name } + } catch (err) { + lastErr = walletError(err, w.name) + // A user-declined prompt is a decision, not a broken wallet: stop rather than prompting again. + if (err && err.code === 4001) throw lastErr + } + } + throw lastErr +} + +function signer() { + if (!_signer) throw new Error('먼저 지갑을 연결하세요') + return _signer +} + +function walletName() { + return _walletName +} + +/** + * Follow the wallet after connecting: switching account or network in MetaMask should update the + * page, not leave it showing a stale address that the next transaction will contradict. + */ +function onWalletChange(handler) { + if (!_injected || typeof _injected.on !== 'function') return + _injected.on('accountsChanged', (accounts) => handler({ accounts: accounts ?? [] })) + _injected.on('chainChanged', () => { + // The cached provider still holds the old network; rebind before anyone signs with it. + _signer = new ethers.providers.Web3Provider(_injected, 'any').getSigner() + handler({ chainChanged: true }) + }) +} + +/** Chain the wallet is currently on, or undefined if it is not one we know. */ +async function currentChainKey() { + if (!_injected) return undefined + const id = String(await _injected.request({ method: 'eth_chainId' })).toLowerCase() + return Object.keys(CHAINS).find((k) => CHAINS[k].chainIdHex.toLowerCase() === id) +} + +/** Switch the connected wallet to `chainKey`, adding the network if it is not known yet. */ +async function ensureChain(chainKey) { + if (!_injected) throw new Error('먼저 지갑을 연결하세요') + const c = CHAINS[chainKey] + const current = await _injected.request({ method: 'eth_chainId' }) + if (String(current).toLowerCase() === c.chainIdHex.toLowerCase()) return + try { + await _injected.request({ method: 'wallet_switchEthereumChain', params: [{ chainId: c.chainIdHex }] }) + } catch (err) { + // 4902 = chain unknown to the wallet; offer to add it rather than dead-ending. + if (err && (err.code === 4902 || (err.data && err.data.originalError && err.data.originalError.code === 4902))) { + await _injected.request({ + method: 'wallet_addEthereumChain', + params: [ + { + chainId: c.chainIdHex, + chainName: c.label, + nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, + rpcUrls: [c.rpc], + blockExplorerUrls: [c.explorer], + }, + ], + }) + } else { + throw walletError(err, _walletName) + } + } + // Re-bind: the provider caches the old network otherwise. + _signer = new ethers.providers.Web3Provider(_injected, 'any').getSigner() +} + +/** Read-only provider per chain, so pages can query without a wallet connected. */ +const _readProviders = {} +function readProvider(chainKey) { + if (!_readProviders[chainKey]) { + _readProviders[chainKey] = new ethers.providers.JsonRpcProvider(CHAINS[chainKey].rpc) + } + return _readProviders[chainKey] +} + +/* ── small DOM helpers ───────────────────────────────────────────────────── */ + +const $ = (sel) => document.querySelector(sel) +const $$ = (sel) => [...document.querySelectorAll(sel)] + +function setStatus(el, text, kind = '') { + el.className = `status ${kind}` + el.innerHTML = text +} + +/** Render a table from rows + column definitions ([label, renderFn]). */ +function table(rows, columns, emptyText = '데이터가 없습니다.') { + if (!rows.length) return `

${emptyText}

` + const head = columns.map(([label]) => `${label}`).join('') + const body = rows + .map((r, i) => `${columns.map(([, render]) => `${render(r, i)}`).join('')}`) + .join('') + return `
${head}${body}
` +} + +/** Wire the shared header: worker/indexer health badges, refreshed on an interval. */ +function mountHeader(active) { + const nav = [ + ['index.html', '전송'], + ['holds.html', '보류 패킷'], + ['overview.html', '종합 현황'], + ['graph.html', '그래프'], + ] + $('#nav').innerHTML = nav + .map(([href, label]) => `${label}`) + .join('') + + /** name + a coloured dot. Detail that used to sit in the label moves to the hover title. */ + const lamp = (name, kind, title) => + `${name}` + + async function refresh() { + const lamps = [] + try { + const s = await workerApi('/status') + const kind = s.state === 'READY' ? 'ok' : s.state === 'HALTED' ? 'bad' : 'warn' + const degraded = s.degraded && s.degraded.length ? `, 축소운영: ${s.degraded.join(',')}` : '' + lamps.push(lamp('worker', kind, `${s.state}${degraded}`)) + } catch { + lamps.push(lamp('worker', 'bad', '중지 — cd worker && pnpm start 으로 실행하세요')) + } + try { + const s = await indexerApi('/api/status') + const feed = s.feed ? `피드 v${s.feed.version}, ${s.feed.entries}건` : '피드 없음' + lamps.push(lamp('indexer', 'ok', `가동 중 · ${feed} · 정책 v${s.policyVersion} · 시드 ${s.seeds}건`)) + } catch { + lamps.push(lamp('indexer', 'bad', '중지')) + } + $('#health').innerHTML = lamps.join('') + } + refresh() + setInterval(refresh, 15000) +} diff --git a/demo/dashboard/overview.html b/demo/dashboard/overview.html new file mode 100644 index 0000000..4501b3a --- /dev/null +++ b/demo/dashboard/overview.html @@ -0,0 +1,268 @@ + + + + + +Compliance DVN — 종합 현황 + + + +
+

Compliance DVN

+ +
+
+ +
+
+ +
+

판정 로그

+
+
+ +
+
+
+

제재 · 믹서 목록

+ + +
+
+
+ +
+

감시 설정

+
+ +

worker에게 제공 중인 피드

+
+
+
+ +
+

근접성 라벨 (실시간)

+
+
+ +
+

최근 토큰 전송 (그래프 엣지)

+
+
+
+ + + + + + + diff --git a/demo/dashboard/serve.py b/demo/dashboard/serve.py new file mode 100644 index 0000000..c0387e3 --- /dev/null +++ b/demo/dashboard/serve.py @@ -0,0 +1,65 @@ +"""Static file server for the demo dashboard, with caching turned off. + + python serve.py [port] # default 8080 + +`python -m http.server` sends Last-Modified and answers conditional requests with 304, so an +edited page keeps serving the old bytes until a hard reload. During a demo that reads as "the +change did not work". Everything here is local and tiny, so no-store costs nothing. +""" + +import sys +from functools import partial +from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path + +CONDITIONAL_HEADERS = ("If-Modified-Since", "If-None-Match", "If-Range") + + +class NoCacheHandler(SimpleHTTPRequestHandler): + """Answers every request in full. + + The conditional request headers are dropped before the base class sees them, so it never + decides to reply 304. Rewriting a 304 into a 200 afterwards would NOT work: the base class + sends no body with a 304, so the client would receive an empty file and a script would load + as zero bytes — which is worse than a stale cache, because nothing in the page reports it. + """ + + def _drop_conditionals(self) -> None: + for header in CONDITIONAL_HEADERS: + while header in self.headers: + del self.headers[header] + + def do_GET(self): # noqa: N802 - name fixed by BaseHTTPRequestHandler + self._drop_conditionals() + super().do_GET() + + def do_HEAD(self): # noqa: N802 + self._drop_conditionals() + super().do_HEAD() + + def send_header(self, keyword, value): + # Without a validator the browser has nothing to revalidate against next time. + if keyword.lower() == "last-modified": + return + super().send_header(keyword, value) + + def end_headers(self): + self.send_header("Cache-Control", "no-store, must-revalidate") + self.send_header("Pragma", "no-cache") + self.send_header("Expires", "0") + super().end_headers() + + def log_message(self, fmt, *args): + sys.stderr.write("%s %s\n" % (self.address_string(), fmt % args)) + + +def main() -> None: + port = int(sys.argv[1]) if len(sys.argv) > 1 else 8080 + handler = partial(NoCacheHandler, directory=str(Path(__file__).parent)) + with ThreadingHTTPServer(("127.0.0.1", port), handler) as httpd: + print(f"dashboard on http://localhost:{port} (no-store)") + httpd.serve_forever() + + +if __name__ == "__main__": + main() diff --git a/demo/dashboard/style.css b/demo/dashboard/style.css new file mode 100644 index 0000000..6bf1d6a --- /dev/null +++ b/demo/dashboard/style.css @@ -0,0 +1,465 @@ +/* + * Pretendard Variable, self-hosted (SIL OFL 1.1 — see fonts/LICENSE-Pretendard.txt). + * + * The variable cut rather than static weights: the UI asks for 400 through 700 including + * in-between values, and one axis renders those exactly instead of snapping to the nearest file. + * Self-hosted rather than from a CDN so the dashboard keeps working offline, like the rest of it. + */ +@font-face { + font-family: 'Pretendard Variable'; + font-weight: 45 920; + font-style: normal; + font-display: swap; + src: url('./fonts/PretendardVariable.woff2') format('woff2-variations'); +} + +/* + * Line-ruled on true black. + * + * Space is divided by hairlines, not by filled cards — nothing has a corner radius, and panels + * carry rules rather than backgrounds. Fills are reserved for state (a verdict, a health badge), + * so on a dense compliance screen the only thing that draws the eye is a decision. + */ +:root { + --bg: #000; + --raise: #0a0a0a; + --line: #232323; + --line-strong: #3a3a3a; + --fg: #ececec; + --muted: #8a8a8a; + --ok: #35c05a; + --warn: #d9a021; + --hold: #a97bf5; + --bad: #ff5449; + --accent: #6cb0ff; +} + +* { box-sizing: border-box; border-radius: 0 !important; } + +body { + margin: 0; + background: var(--bg); + color: var(--fg); + /* One face covers Hangul and Latin, so mixed text no longer falls back per-glyph into two + mismatched weights — which is what made "피드 v121" look like two fonts. */ + font: 14px/1.65 'Pretendard Variable', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; + word-break: keep-all; /* wrap Korean at word boundaries, not mid-word */ + -webkit-font-smoothing: antialiased; +} + +/* + * Tabular figures in tables only. + * + * Applied to the whole page it widened every narrow digit — Pretendard's proportional "1" is much + * narrower than its tabular one, so a lone 1 in a stat tile sat in a gap of its own side bearings + * and read as the wrong font. Columns still need the alignment; prose and headline numbers do not. + */ +th, td { font-variant-numeric: tabular-nums; } + +/* ── header ─────────────────────────────────────────────────────────────── */ + +header { + display: flex; + flex-wrap: wrap; + gap: 10px 22px; + align-items: center; + padding: 13px 22px; + border-bottom: 1px solid var(--line-strong); + background: var(--bg); + position: sticky; + top: 0; + z-index: 10; +} + +/* + * No `text-transform: uppercase` anywhere below. + * + * The UI is Korean with Latin identifiers embedded in it (testUSDT, mint(), 피드 v121). Uppercase + * does nothing to Hangul and mangles those identifiers into TESTUSDT and V121. Wide letter-spacing + * is likewise held back: it separates Hangul syllables that should read as one word. + */ +header h1 { + font-size: 14px; + margin: 0; + font-weight: 700; + letter-spacing: .4px; +} +header h1 span { color: var(--muted); font-weight: 400; } + +/* + * The nav spans the header's full height: negative margins cancel the header's vertical padding, + * so the dividers between tabs run edge to edge instead of floating in the middle of the bar. + */ +#nav { + display: flex; + align-self: stretch; + margin: -13px 0; +} +#nav a { + display: flex; + align-items: center; + color: var(--muted); + text-decoration: none; + padding: 0 16px; + font-weight: 550; + border-left: 1px solid var(--line); +} +#nav a:last-child { border-right: 1px solid var(--line); } +#nav a:hover { color: var(--fg); } +#nav a.on { color: var(--fg); box-shadow: inset 0 -2px 0 var(--accent); } + +#health { margin-left: auto; display: flex; gap: 18px; flex-wrap: wrap; } + +/* Connected wallet: who signs on the first line, what they hold on the second. */ +.wallet { display: flex; flex-direction: column; gap: 3px; } + +/* Service state as a name plus a lamp; the detail lives on the hover title. */ +.lamp { display: inline-flex; align-items: center; gap: 7px; color: var(--muted); font-size: 12.5px; cursor: help; } +.dot { + width: 8px; + height: 8px; + /* Overrides the global radius reset — a status lamp is the one thing that should be round. */ + border-radius: 50% !important; + background: var(--line-strong); +} +.dot.ok { background: var(--ok); box-shadow: 0 0 6px rgba(53, 192, 90, .7); } +.dot.warn { background: var(--warn); box-shadow: 0 0 6px rgba(217, 160, 33, .6); } +.dot.bad { background: var(--bad); box-shadow: 0 0 6px rgba(255, 84, 73, .6); } + +main { padding: 0 22px 40px; max-width: 1600px; margin: 0 auto; } + +/* ── headings ───────────────────────────────────────────────────────────── */ + +h2 { + font-size: 13.5px; + margin: 0 0 5px; + font-weight: 700; + letter-spacing: .2px; + /* The help marker sits inside the heading; keep it on the text baseline, not stretched. */ + display: flex; + align-items: center; +} +h3 { + font-size: 12px; + margin: 0 0 8px; + color: var(--muted); + font-weight: 600; + display: flex; + align-items: center; +} + +/* ── panels: ruled, not boxed ───────────────────────────────────────────── */ + +.grid { display: grid; gap: 0; } +.grid.two { grid-template-columns: repeat(auto-fit, minmax(440px, 1fr)); } +/* 150px, not 210: inside a half-width card the third column no longer wraps to its own row — + which is what actually pushed 수량 out of line with the two selects beside it. */ +.grid.three { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 0 22px; } +.grid.stats { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); } + +/* + * A row of panels is one framed area: outer edges on both sides, a divider between columns. + * + * The frame goes on the grid rather than on each card. Per-card side borders would double up at + * every seam and leave the last column open on the right, which is what made the row read as a + * set of loose boxes instead of one region. + */ +.card { + border-top: 1px solid var(--line-strong); + border-left: 1px solid var(--line); + border-right: 1px solid var(--line); + padding: 18px 24px 26px 22px; +} + +/* In a row, the frame belongs to the grid; the cards inside contribute only the dividers. */ +.grid.two, +.grid.stats { + border-left: 1px solid var(--line); + border-right: 1px solid var(--line); +} +.grid.two > .card, +.grid.stats > .card { border-left: 0; border-right: 0; } +.grid.two > .card:not(:first-child), +.grid.stats > .card:not(:first-child) { border-left: 1px solid var(--line); } + +/* Nothing follows the last region, so it closes itself. */ +main > :last-child { border-bottom: 1px solid var(--line); } + +/* Stacked, there is no inner edge to divide — a left rule would be a stray vertical line. */ +@media (max-width: 1180px) { + .grid.two > .card:not(:first-child) { border-left: 0; } +} +.card.tight { padding: 14px 0 12px 22px; } + +/* + * Regions sit flush against each other, sharing one rule at every seam. + * + * A margin between them left the columns' vertical rules ending in mid-air with nothing closing + * them, and doubled the gap under the stat tiles. Flush, each region's top rule closes the one + * above it and the frame reads as continuous down the page. + */ +main > .card, +main > .grid { margin-top: 0; } + +/* Proportional figures: these are read as words, not compared down a column. */ +.stat .value { font-size: 27px; font-weight: 600; line-height: 1.1; } +.stat .label { color: var(--muted); font-size: 11.5px; margin-top: 3px; } + +/* ── form controls: underlined, not boxed ───────────────────────────────── */ + +label { + display: block; + font-size: 11.5px; + color: var(--muted); + margin: 14px 0 4px; + font-weight: 600; +} + +input, select, textarea { + width: 100%; + padding: 7px 2px; + background: transparent; + border: 0; + border-bottom: 1px solid var(--line-strong); + color: var(--fg); + font: inherit; + line-height: 1.4; +} +input:focus, select:focus { outline: none; border-bottom-color: var(--accent); } +input::placeholder { color: #4a4a4a; } + +/* + * Strip the native select chrome. + * + * Left alone, a select carries its own border and intrinsic height, so it sat a few pixels off the + * inputs beside it — the row of 출발 / 도착 / 수량 never lined up. Same box model now, with the + * dropdown arrow drawn as a background so nothing reintroduces a border. + */ +select { + appearance: none; + -webkit-appearance: none; + padding-right: 18px; + background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%), + linear-gradient(135deg, var(--muted) 50%, transparent 50%); + background-position: calc(100% - 9px) calc(50% + 1px), calc(100% - 4px) calc(50% + 1px); + background-size: 5px 5px, 5px 5px; + background-repeat: no-repeat; +} +select option { background: var(--raise); } + +/* Grid cells whose label heights differ would still misalign; anchor the control to the bottom. */ +.field { display: flex; flex-direction: column; justify-content: flex-end; } + +/* Fixed advance for hex only — addresses and hashes are compared by eye, column by column. */ +.mono, input.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13.5px; } + +button { + padding: 7px 16px; + background: transparent; + color: var(--fg); + border: 1px solid var(--fg); + font: inherit; + font-weight: 600; + letter-spacing: .2px; + cursor: pointer; +} +button:hover:not(:disabled) { background: var(--fg); color: #000; } +button:disabled { opacity: .35; cursor: not-allowed; } +button.ghost { border-color: var(--line-strong); color: var(--muted); } +button.ghost:hover:not(:disabled) { background: transparent; border-color: var(--fg); color: var(--fg); } +button.danger { border-color: var(--bad); color: var(--bad); } +button.danger:hover:not(:disabled) { background: var(--bad); color: #000; } +button.sm { padding: 3px 10px; font-size: 12px; } + +.row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; } +.row.end { justify-content: flex-end; } +.spacer { flex: 1; } + +/* ── state markers ──────────────────────────────────────────────────────── */ + +/* + * State and labels are both bordered rectangles — no left accent bars anywhere. + * + * A bar reads as a divider in a layout already built from rules, so state was competing with the + * lines that organise the page. A box says "this is a value" instead. Colour still separates a + * verdict from a mere label. + * + * These carry Korean readings, so they inherit the body face: hex needs fixed advance, words do + * not, and Hangul in a Latin monospace stack falls back per glyph into a mismatched weight. + */ +.badge { + display: inline-block; + padding: 1px 8px; + border: 1px solid var(--line-strong); + font-size: 12.5px; + font-weight: 600; + color: var(--muted); + white-space: nowrap; +} +.badge.ok { color: var(--ok); border-color: rgba(53, 192, 90, .5); } +.badge.warn { color: var(--warn); border-color: rgba(217, 160, 33, .5); } +.badge.hold { color: var(--hold); border-color: rgba(169, 123, 245, .55); } +.badge.bad { color: var(--bad); border-color: rgba(255, 84, 73, .55); } +.badge.muted { color: var(--muted); border-color: var(--line); } + +/* + * Copy button. Two overlapping squares drawn in CSS rather than an icon font or an inline SVG per + * row — there is one of these on every address, and the shape is two rectangles. + */ +.copy { + position: relative; + width: 20px; + height: 20px; + padding: 0; + margin-left: 7px; + border: 0; + background: transparent; + vertical-align: -5px; + cursor: pointer; +} +.copy::before, .copy::after { + content: ''; + position: absolute; + border: 1px solid var(--muted); +} +.copy::before { width: 8px; height: 9px; left: 3px; top: 3px; } +.copy::after { width: 8px; height: 9px; left: 7px; top: 6px; background: var(--bg); } +.copy:hover { background: transparent; } +.copy:hover::before, .copy:hover::after { border-color: var(--fg); } +.copy.failed::before, .copy.failed::after { border-color: var(--bad); } +/* Confirmed state: a tick, so the click is acknowledged without a toast. */ +.copy.copied::before { border-color: var(--ok); } +.copy.copied::after { + border-color: var(--ok); + border-top: 0; + border-right: 0; + width: 9px; + height: 5px; + left: 5px; + top: 5px; + background: transparent; + transform: rotate(-45deg); +} + +.chip { + display: inline-block; + padding: 1px 8px; + margin: 2px 3px 2px 0; + border: 1px solid var(--line); + font-size: 12.5px; + color: var(--muted); +} +.chip-risk { border-color: rgba(255, 84, 73, .55); color: #ff9b93; } + +.status { margin-top: 14px; font-size: 13.5px; min-height: 20px; } +.status.ok { color: var(--ok); } +.status.err { color: var(--bad); } +.status.busy { color: var(--warn); } + +.muted { color: var(--muted); } +.right { text-align: right; } +p.muted { font-size: 12.5px; max-width: 100ch; } + +/* ── help marker: rationale on demand ───────────────────────────────────── */ + +.help { + display: inline-flex; + align-items: center; + justify-content: center; + width: 15px; + height: 15px; + margin-left: 8px; + border: 1px solid var(--line-strong); + color: var(--muted); + font-size: 11px; + font-weight: 700; + cursor: help; + position: relative; + vertical-align: 1px; +} +.help::after { content: '?'; } +.help:hover, .help:focus { border-color: var(--accent); color: var(--accent); outline: none; } + +.help-card { + /* Hidden rather than display:none so screen readers still reach the content. Shown instantly: + a fade would make the reveal depend on the compositor, and a dashboard gains nothing from + waiting 120ms to answer a question. */ + visibility: hidden; + opacity: 0; + position: absolute; + top: calc(100% + 8px); + left: -8px; + z-index: 30; + /* Plain px, no nested min()/calc(): the engine here resolves `min(400px, calc(100vw - 60px))` + to 0 and collapses the card. A media query below handles narrow viewports instead. */ + width: 400px; + padding: 13px 15px; + background: #050505; + border: 1px solid var(--line-strong); + color: var(--fg); + font-size: 13px; + font-weight: 400; + line-height: 1.6; + text-align: left; + white-space: normal; + cursor: auto; + box-shadow: 0 8px 28px rgba(0, 0, 0, .85); +} +.help:hover .help-card, .help:focus .help-card { visibility: visible; opacity: 1; } +.help-card p { margin: 0 0 8px; } +.help-card p:last-child { margin: 0; } +.help-card .mono { font-size: 12px; } + +/* A marker near the right edge opens leftward, so the card cannot run off-screen. */ +.help.left .help-card { left: auto; right: -8px; } + +@media (max-width: 520px) { + .help-card { width: 280px; } +} + +/* ── tables ─────────────────────────────────────────────────────────────── */ + +/* Rows run about 10% larger than the surrounding chrome: the lists are what gets read on a + projector or in a video frame, so they carry a little more weight than the panel titles. */ +.table-wrap { overflow-x: auto; margin-top: 12px; } +table { width: 100%; border-collapse: collapse; font-size: 14.5px; } +th, td { text-align: left; padding: 6px 17px 6px 0; border-bottom: 1px solid var(--line); vertical-align: top; } +th { + color: var(--muted); + font-size: 12.5px; + font-weight: 600; + white-space: nowrap; + border-bottom-color: var(--line-strong); +} +tbody tr:hover td { background: var(--raise); } +/* Sits under a table whose last row already drew a rule, so the pager adds none of its own. */ +.row.pager { gap: 10px; padding-top: 10px; font-size: 13px; } +a { color: var(--accent); text-decoration: none; } +a:hover { text-decoration: underline; } + +/* ── packet tracker ─────────────────────────────────────────────────────── */ + +.track { display: flex; flex-direction: column; } +/* No left accent bar: the state badge in the head already names the outcome. */ +.track-item { + border-bottom: 1px solid var(--line); + padding: 14px 0; +} +.track-head { display: flex; gap: 14px; align-items: center; flex-wrap: wrap; } +.steps { display: flex; gap: 8px; align-items: center; margin-top: 8px; font-size: 13.5px; color: var(--muted); flex-wrap: wrap; } +.steps .step { font-size: 13.5px; } +.steps .step.done { color: var(--fg); } +.steps .sep { opacity: .35; } + +/* ── graph ──────────────────────────────────────────────────────────────── */ + +#graph { width: 100%; height: 620px; background: var(--bg); border: 1px solid var(--line); margin-top: 14px; } +.legend { display: flex; gap: 18px; flex-wrap: wrap; font-size: 12px; margin-top: 12px; color: var(--muted); } +.legend i { display: inline-block; width: 9px; height: 9px; margin-right: 6px; vertical-align: 0; } +.node-label { font-size: 11px; fill: var(--fg); font-family: ui-monospace, Menlo, monospace; pointer-events: none; } +.edge { stroke: #333; stroke-width: 1.3px; } +.edge.tainted { stroke: var(--bad); stroke-width: 2px; } +/* Dashed: the two ends of a bridged transfer are not on the same chain. */ +.edge.bridged { stroke-dasharray: 5 4; } + +details summary { cursor: pointer; color: var(--muted); font-size: 12.5px; } diff --git a/demo/deploy/FakeStablecoinMock.ts b/demo/deploy/FakeStablecoinMock.ts new file mode 100644 index 0000000..67544fe --- /dev/null +++ b/demo/deploy/FakeStablecoinMock.ts @@ -0,0 +1,26 @@ +import { type DeployFunction } from 'hardhat-deploy/types' + +// Demo sources live outside `contracts/` and are not compiled by the main pipeline; the deploy +// uses the artifact checked in next to them (see demo/README.md to rebuild it). +import artifact from '../prebuilt/FakeStablecoinMock.json' + +/** + * Demo decoy for the impersonation check. Testnet only. + * + * Deployed on both chains so it can stand in as a recipient in either direction: the OFT recipient + * is screened against the DESTINATION chain's state, so the decoy has to exist there. + */ +const deploy: DeployFunction = async (hre) => { + const { deployer } = await hre.getNamedAccounts() + const { address } = await hre.deployments.deploy('FakeStablecoinMock', { + contract: { abi: artifact.abi, bytecode: artifact.bytecode }, + from: deployer, + args: [], + log: true, + skipIfAlreadyDeployed: false, + }) + console.log(`FakeStablecoinMock on ${hre.network.name}: ${address}`) +} + +deploy.tags = ['FakeStablecoinMock'] +export default deploy diff --git a/demo/deploy/FakeUsdcOFT.ts b/demo/deploy/FakeUsdcOFT.ts new file mode 100644 index 0000000..c43ac3c --- /dev/null +++ b/demo/deploy/FakeUsdcOFT.ts @@ -0,0 +1,30 @@ +import { type DeployFunction } from 'hardhat-deploy/types' + +/** + * A sendable decoy stablecoin. Testnet only. + * + * The impersonation check runs on the token being MOVED, not on the recipient: the engine resolves + * a packet's OApp through `token()` and compares that token against the chain's canonical issuer. + * Demonstrating it therefore needs a fake stablecoin that can actually be sent cross-chain — which + * means a wired OFT, not a plain contract. + * + * This is the same `MyOFT` code as the demo token, deployed under its own name so it gets its own + * address and its own wiring. Wire it with OAPP_CONTRACT=FakeUsdcOFT. + */ +const deploy: DeployFunction = async (hre) => { + const { deployer } = await hre.getNamedAccounts() + const endpointV2 = await hre.deployments.get('EndpointV2') + + const { address } = await hre.deployments.deploy('FakeUsdcOFT', { + contract: 'MyOFT', + from: deployer, + // 'USDC' is what makes it a decoy: a watched symbol from an address that is not Circle's. + args: ['USD Coin', 'USDC', endpointV2.address, deployer], + log: true, + skipIfAlreadyDeployed: false, + }) + console.log(`FakeUsdcOFT (symbol USDC) on ${hre.network.name}: ${address}`) +} + +deploy.tags = ['FakeUsdcOFT'] +export default deploy diff --git a/demo/deploy/RiskyProxyMock.ts b/demo/deploy/RiskyProxyMock.ts new file mode 100644 index 0000000..c2d3a95 --- /dev/null +++ b/demo/deploy/RiskyProxyMock.ts @@ -0,0 +1,32 @@ +import { type DeployFunction } from 'hardhat-deploy/types' + +// Demo sources live outside `contracts/` and are not compiled by the main pipeline; the deploy +// uses the artifact checked in next to them (see demo/README.md to rebuild it). +import artifact from '../prebuilt/RiskyProxyMock.json' + +/** + * Demo decoy for the admin-risk check. Testnet only. + * + * The admin defaults to the address the worker's TEST_DENYLIST carries, since the check only fires + * when the admin is one the risk store has something to say about. Override with RISKY_ADMIN to + * point it at an OFAC address instead. + */ +const DEFAULT_RISKY_ADMIN = '0x000000000000000000000000000000000000dEaD' + +const deploy: DeployFunction = async (hre) => { + const { deployer } = await hre.getNamedAccounts() + const admin = (process.env.RISKY_ADMIN ?? '').trim() || DEFAULT_RISKY_ADMIN + + // Any non-zero address makes the implementation slot set, which is what marks it upgradeable. + const { address } = await hre.deployments.deploy('RiskyProxyMock', { + contract: { abi: artifact.abi, bytecode: artifact.bytecode }, + from: deployer, + args: [admin, deployer], + log: true, + skipIfAlreadyDeployed: false, + }) + console.log(`RiskyProxyMock on ${hre.network.name}: ${address} (admin ${admin})`) +} + +deploy.tags = ['RiskyProxyMock'] +export default deploy diff --git a/demo/prebuilt/FakeStablecoinMock.json b/demo/prebuilt/FakeStablecoinMock.json new file mode 100644 index 0000000..2f62139 --- /dev/null +++ b/demo/prebuilt/FakeStablecoinMock.json @@ -0,0 +1,355 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "FakeStablecoinMock", + "sourceName": "contracts/mocks/FakeStablecoinMock.sol", + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "allowance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientAllowance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "ERC20InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC20InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "ERC20InvalidSender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "ERC20InvalidSpender", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b50604051806040016040528060088152602001672aa9a21021b7b4b760c11b815250604051806040016040528060048152602001635553444360e01b815250816003908161005e9190610114565b50600461006b8282610114565b5050506101d3565b634e487b7160e01b600052604160045260246000fd5b600181811c9082168061009d57607f821691505b6020821081036100bd57634e487b7160e01b600052602260045260246000fd5b50919050565b601f82111561010f576000816000526020600020601f850160051c810160208610156100ec5750805b601f850160051c820191505b8181101561010b578281556001016100f8565b5050505b505050565b81516001600160401b0381111561012d5761012d610073565b6101418161013b8454610089565b846100c3565b602080601f831160018114610176576000841561015e5750858301515b600019600386901b1c1916600185901b17855561010b565b600085815260208120601f198616915b828110156101a557888601518255948401946001909101908401610186565b50858210156101c35787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b61079f806101e26000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c806340c10f191161007157806340c10f191461012357806370a082311461013857806395d89b4114610161578063a9059cbb14610169578063dd62ed3e1461017c578063fc0c546a146101b557600080fd5b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100ef57806323b872dd14610101578063313ce56714610114575b600080fd5b6100b66101c3565b6040516100c391906105e8565b60405180910390f35b6100df6100da366004610653565b610255565b60405190151581526020016100c3565b6002545b6040519081526020016100c3565b6100df61010f36600461067d565b61026f565b604051600681526020016100c3565b610136610131366004610653565b610293565b005b6100f36101463660046106b9565b6001600160a01b031660009081526020819052604090205490565b6100b66102a1565b6100df610177366004610653565b6102b0565b6100f361018a3660046106db565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6040513081526020016100c3565b6060600380546101d29061070e565b80601f01602080910402602001604051908101604052809291908181526020018280546101fe9061070e565b801561024b5780601f106102205761010080835404028352916020019161024b565b820191906000526020600020905b81548152906001019060200180831161022e57829003601f168201915b5050505050905090565b6000336102638185856102be565b60019150505b92915050565b60003361027d8582856102d0565b610288858585610354565b506001949350505050565b61029d82826103b3565b5050565b6060600480546101d29061070e565b600033610263818585610354565b6102cb83838360016103e9565b505050565b6001600160a01b0383811660009081526001602090815260408083209386168352929052205460001981101561034e578181101561033f57604051637dc7a0d960e11b81526001600160a01b038416600482015260248101829052604481018390526064015b60405180910390fd5b61034e848484840360006103e9565b50505050565b6001600160a01b03831661037e57604051634b637e8f60e11b815260006004820152602401610336565b6001600160a01b0382166103a85760405163ec442f0560e01b815260006004820152602401610336565b6102cb8383836104be565b6001600160a01b0382166103dd5760405163ec442f0560e01b815260006004820152602401610336565b61029d600083836104be565b6001600160a01b0384166104135760405163e602df0560e01b815260006004820152602401610336565b6001600160a01b03831661043d57604051634a1406b160e11b815260006004820152602401610336565b6001600160a01b038085166000908152600160209081526040808320938716835292905220829055801561034e57826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040516104b091815260200190565b60405180910390a350505050565b6001600160a01b0383166104e95780600260008282546104de9190610748565b9091555061055b9050565b6001600160a01b0383166000908152602081905260409020548181101561053c5760405163391434e360e21b81526001600160a01b03851660048201526024810182905260448101839052606401610336565b6001600160a01b03841660009081526020819052604090209082900390555b6001600160a01b03821661057757600280548290039055610596565b6001600160a01b03821660009081526020819052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516105db91815260200190565b60405180910390a3505050565b60006020808352835180602085015260005b81811015610616578581018301518582016040015282016105fa565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b038116811461064e57600080fd5b919050565b6000806040838503121561066657600080fd5b61066f83610637565b946020939093013593505050565b60008060006060848603121561069257600080fd5b61069b84610637565b92506106a960208501610637565b9150604084013590509250925092565b6000602082840312156106cb57600080fd5b6106d482610637565b9392505050565b600080604083850312156106ee57600080fd5b6106f783610637565b915061070560208401610637565b90509250929050565b600181811c9082168061072257607f821691505b60208210810361074257634e487b7160e01b600052602260045260246000fd5b50919050565b8082018082111561026957634e487b7160e01b600052601160045260246000fdfea2646970667358221220c4840d9c77eca3ef0ead0a97a130e6b366b42fde6b4af36b1a94a13b78cc069664736f6c63430008160033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100a95760003560e01c806340c10f191161007157806340c10f191461012357806370a082311461013857806395d89b4114610161578063a9059cbb14610169578063dd62ed3e1461017c578063fc0c546a146101b557600080fd5b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100ef57806323b872dd14610101578063313ce56714610114575b600080fd5b6100b66101c3565b6040516100c391906105e8565b60405180910390f35b6100df6100da366004610653565b610255565b60405190151581526020016100c3565b6002545b6040519081526020016100c3565b6100df61010f36600461067d565b61026f565b604051600681526020016100c3565b610136610131366004610653565b610293565b005b6100f36101463660046106b9565b6001600160a01b031660009081526020819052604090205490565b6100b66102a1565b6100df610177366004610653565b6102b0565b6100f361018a3660046106db565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6040513081526020016100c3565b6060600380546101d29061070e565b80601f01602080910402602001604051908101604052809291908181526020018280546101fe9061070e565b801561024b5780601f106102205761010080835404028352916020019161024b565b820191906000526020600020905b81548152906001019060200180831161022e57829003601f168201915b5050505050905090565b6000336102638185856102be565b60019150505b92915050565b60003361027d8582856102d0565b610288858585610354565b506001949350505050565b61029d82826103b3565b5050565b6060600480546101d29061070e565b600033610263818585610354565b6102cb83838360016103e9565b505050565b6001600160a01b0383811660009081526001602090815260408083209386168352929052205460001981101561034e578181101561033f57604051637dc7a0d960e11b81526001600160a01b038416600482015260248101829052604481018390526064015b60405180910390fd5b61034e848484840360006103e9565b50505050565b6001600160a01b03831661037e57604051634b637e8f60e11b815260006004820152602401610336565b6001600160a01b0382166103a85760405163ec442f0560e01b815260006004820152602401610336565b6102cb8383836104be565b6001600160a01b0382166103dd5760405163ec442f0560e01b815260006004820152602401610336565b61029d600083836104be565b6001600160a01b0384166104135760405163e602df0560e01b815260006004820152602401610336565b6001600160a01b03831661043d57604051634a1406b160e11b815260006004820152602401610336565b6001600160a01b038085166000908152600160209081526040808320938716835292905220829055801561034e57826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040516104b091815260200190565b60405180910390a350505050565b6001600160a01b0383166104e95780600260008282546104de9190610748565b9091555061055b9050565b6001600160a01b0383166000908152602081905260409020548181101561053c5760405163391434e360e21b81526001600160a01b03851660048201526024810182905260448101839052606401610336565b6001600160a01b03841660009081526020819052604090209082900390555b6001600160a01b03821661057757600280548290039055610596565b6001600160a01b03821660009081526020819052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516105db91815260200190565b60405180910390a3505050565b60006020808352835180602085015260005b81811015610616578581018301518582016040015282016105fa565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b038116811461064e57600080fd5b919050565b6000806040838503121561066657600080fd5b61066f83610637565b946020939093013593505050565b60008060006060848603121561069257600080fd5b61069b84610637565b92506106a960208501610637565b9150604084013590509250925092565b6000602082840312156106cb57600080fd5b6106d482610637565b9392505050565b600080604083850312156106ee57600080fd5b6106f783610637565b915061070560208401610637565b90509250929050565b600181811c9082168061072257607f821691505b60208210810361074257634e487b7160e01b600052602260045260246000fd5b50919050565b8082018082111561026957634e487b7160e01b600052601160045260246000fdfea2646970667358221220c4840d9c77eca3ef0ead0a97a130e6b366b42fde6b4af36b1a94a13b78cc069664736f6c63430008160033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/demo/prebuilt/RiskyProxyMock.json b/demo/prebuilt/RiskyProxyMock.json new file mode 100644 index 0000000..f243097 --- /dev/null +++ b/demo/prebuilt/RiskyProxyMock.json @@ -0,0 +1,53 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "RiskyProxyMock", + "sourceName": "contracts/mocks/RiskyProxyMock.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_admin", + "type": "address" + }, + { + "internalType": "address", + "name": "_implementation", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address", + "name": "a", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "i", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b5060405161024838038061024883398101604081905261002f91610135565b6001600160a01b0382166100775760405162461bcd60e51b815260206004820152600a6024820152693d32b9379030b236b4b760b11b60448201526064015b60405180910390fd5b6001600160a01b0381166100cd5760405162461bcd60e51b815260206004820152601360248201527f7a65726f20696d706c656d656e746174696f6e00000000000000000000000000604482015260640161006e565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103919091557f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc55610168565b80516001600160a01b038116811461013057600080fd5b919050565b6000806040838503121561014857600080fd5b61015183610119565b915061015f60208401610119565b90509250929050565b60d2806101766000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c80635c60da1b146037578063f851a440146076575b600080fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545b6040516001600160a01b03909116815260200160405180910390f35b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610354605a56fea2646970667358221220db4d640c5bd6b171ede814a8f22af946c7a8d98b7ba3fb5d90d436509788860864736f6c63430008160033", + "deployedBytecode": "0x6080604052348015600f57600080fd5b506004361060325760003560e01c80635c60da1b146037578063f851a440146076575b600080fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545b6040516001600160a01b03909116815260200160405180910390f35b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610354605a56fea2646970667358221220db4d640c5bd6b171ede814a8f22af946c7a8d98b7ba3fb5d90d436509788860864736f6c63430008160033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/demo/script/mintDemo.ts b/demo/script/mintDemo.ts new file mode 100644 index 0000000..bd8b531 --- /dev/null +++ b/demo/script/mintDemo.ts @@ -0,0 +1,61 @@ +import { deployments, ethers } from 'hardhat' + +/** + * Mint demo balances: 100 tokens to each demo wallet on the target network. + * + * npx hardhat run demo/script/mintDemo.ts --network base-sepolia + * TOKEN=FakeUsdcOFT npx hardhat run demo/script/mintDemo.ts --network base-sepolia + * + * `mint` is open on these testnet tokens, so any funded signer can run this. + */ +const TOKEN = (process.env.TOKEN ?? '').trim() || 'MyOFT' +const DEMO_WALLETS: Record = { + owner: '0x8583894d0e57e42abb83039537f314490038efa0', + worker: '0x01D24AE2cD8ad18472BD00AfE4ec425E800e184d', + feed: '0xcD346e8762E27d0558a260C1c3562127c52Ad45b', + clean: '0x25D10657a2642Fe8cd6bEe501dBd0939d79bD90F', + onehop: '0x9a1c282ebA5e9A97290cAc530902Fb00dcf2ECe2', +} + +const AMOUNT = ethers.utils.parseEther('100') + +const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)) + +/** Public RPCs cap in-flight txs per account, so sends are retried with a pause. */ +async function mintWithRetry(oft: Awaited>, to: string): Promise { + for (let attempt = 1; ; attempt++) { + try { + const tx = await oft.mint(to, AMOUNT) + await tx.wait() + return tx.hash + } catch (err) { + if (attempt >= 5) throw err + await sleep(5000) + } + } +} + +async function main(): Promise { + const { address } = await deployments.get(TOKEN) + // Both demo tokens are MyOFT instances, so one ABI covers them. + const oft = await ethers.getContractAt('MyOFT', address) + console.log(`token ${address} name=${await oft.name()} symbol=${await oft.symbol()}`) + + for (const [label, to] of Object.entries(DEMO_WALLETS)) { + // Idempotent: a wallet that already holds the demo balance is not topped up again. + if ((await oft.balanceOf(to)).gte(AMOUNT)) { + console.log(`skip -> ${label.padEnd(6)} ${to} (already funded)`) + continue + } + const hash = await mintWithRetry(oft, to) + console.log(`minted 100 -> ${label.padEnd(6)} ${to} tx=${hash}`) + } + for (const [label, to] of Object.entries(DEMO_WALLETS)) { + console.log(`${label.padEnd(6)} balance: ${ethers.utils.formatEther(await oft.balanceOf(to))}`) + } +} + +main().catch((err) => { + console.error(err) + process.exit(1) +}) diff --git a/deploy/ComplianceDVN.ts b/deploy/ComplianceDVN.ts index e8308af..5f59c84 100644 --- a/deploy/ComplianceDVN.ts +++ b/deploy/ComplianceDVN.ts @@ -2,20 +2,45 @@ import { parseEther } from 'ethers/lib/utils' import { type HardhatRuntimeEnvironment } from 'hardhat/types' import { type DeployFunction } from 'hardhat-deploy/types' +const SEND_ULN: Record = { + 40245: '0xC1868e054425D378095A003EcbA3823a5D0135C9', // base-sepolia + 40232: '0xB31D2cb502E25B30C651842C7C3293c51Fe6d16f', // optimism-sepolia +} + const RECEIVE_ULN: Record = { 40245: '0x12523de19dc41c91F7d2093E0CFbB76b17012C8d', // base-sepolia 40232: '0x9284fd59B95b9143AF0b9795CAC16eb3C723C9Ca', // optimism-sepolia } +const EVM_ADDRESS = /^0x[0-9a-fA-F]{40}$/ + const deploy: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { const { deploy } = hre.deployments const { deployer } = await hre.getNamedAccounts() const eid = (hre.network.config as any).eid as number + const sendUln = SEND_ULN[eid] + if (!sendUln) throw new Error(`no SendUln302 for eid ${eid}`) const receiveUln = RECEIVE_ULN[eid] if (!receiveUln) throw new Error(`no ReceiveUln302 for eid ${eid}`) + + // The owner approves packets held for manual review; the operator (the worker's key) + // verifies them. Keeping them distinct is what stops the worker from releasing its own + // holds — see `approvePacket` in ComplianceDVN.sol. Set OPERATOR_ADDRESS to the worker's + // key to get that separation; without it both roles collapse onto the deployer, which is + // fine for a demo but gives the worker approval rights. + const operator = (process.env.OPERATOR_ADDRESS ?? '').trim() || deployer + if (!EVM_ADDRESS.test(operator)) { + throw new Error(`OPERATOR_ADDRESS must be a 20-byte EVM address, got '${operator}'`) + } + if (operator.toLowerCase() === deployer.toLowerCase()) { + console.warn( + `[ComplianceDVN] operator == owner (${deployer}). The worker will be able to approve its own held packets. Set OPERATOR_ADDRESS to separate the roles.` + ) + } + await deploy('ComplianceDVN', { from: deployer, - args: [deployer, deployer, receiveUln, parseEther('0.00005')], + args: [deployer, operator, sendUln, receiveUln, parseEther('0.00005')], log: true, }) } diff --git a/deploy/MyOFT.ts b/deploy/MyOFT.ts index 8970e45..40c2a29 100644 --- a/deploy/MyOFT.ts +++ b/deploy/MyOFT.ts @@ -36,8 +36,11 @@ const deploy: DeployFunction = async (hre) => { const { address } = await deploy(contractName, { from: deployer, args: [ - 'MyOFT', // name - 'MOFT', // symbol + // Demo token identity. 'testUSDT' upper-cases to TESTUSDT, which is deliberately NOT + // in the worker's WATCHED_STABLE_SYMBOLS — the demo token must not flag itself as a + // stablecoin impersonator. + 'testUSDT', // name + 'testUSDT', // symbol endpointV2Deployment.address, // LayerZero's EndpointV2 address deployer, // owner ], diff --git a/deployments/base-sepolia/ComplianceDVN.json b/deployments/base-sepolia/ComplianceDVN.json index 65dce4a..e709659 100644 --- a/deployments/base-sepolia/ComplianceDVN.json +++ b/deployments/base-sepolia/ComplianceDVN.json @@ -1,5 +1,5 @@ { - "address": "0x5d5B0c36D1e522C0BB44fdd6402576De42484Ee0", + "address": "0x497E0962BeD72DC12Fb249995cA618a929C0d17A", "abi": [ { "inputs": [ @@ -13,6 +13,11 @@ "name": "_operator", "type": "address" }, + { + "internalType": "address", + "name": "_sendUln", + "type": "address" + }, { "internalType": "address", "name": "_receiveUln", @@ -27,11 +32,21 @@ "stateMutability": "nonpayable", "type": "constructor" }, + { + "inputs": [], + "name": "AllowNotSeparatelyRecorded", + "type": "error" + }, { "inputs": [], "name": "NotOperator", "type": "error" }, + { + "inputs": [], + "name": "NotSendLibrary", + "type": "error" + }, { "inputs": [ { @@ -54,6 +69,28 @@ "name": "OwnableUnauthorizedAccount", "type": "error" }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "action", + "type": "uint8" + } + ], + "name": "UnknownAction", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "action", + "type": "uint8" + } + ], + "name": "VerificationRequiresAllow", + "type": "error" + }, { "anonymous": false, "inputs": [ @@ -130,6 +167,25 @@ "name": "OwnershipTransferred", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "payloadHash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "PacketApproved", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -143,6 +199,121 @@ "name": "ReceiveUlnSet", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "payloadHash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "action", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "score", + "type": "uint16" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reasonMask", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "evidenceHash", + "type": "bytes32" + } + ], + "name": "RiskVerdict", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "sendUln", + "type": "address" + } + ], + "name": "SendUlnSet", + "type": "event" + }, + { + "inputs": [], + "name": "ACTION_ALLOW", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ACTION_BLOCK", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ACTION_DELAY", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ACTION_MANUAL_REVIEW", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "payloadHash", + "type": "bytes32" + } + ], + "name": "approvePacket", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -280,6 +451,39 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "payloadHash", + "type": "bytes32" + }, + { + "internalType": "uint8", + "name": "action", + "type": "uint8" + }, + { + "internalType": "uint16", + "name": "score", + "type": "uint16" + }, + { + "internalType": "uint256", + "name": "reasonMask", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "evidenceHash", + "type": "bytes32" + } + ], + "name": "recordVerdict", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "renounceOwnership", @@ -287,6 +491,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "sendUln", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -326,6 +543,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "_sendUln", + "type": "address" + } + ], + "name": "setSendUln", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -342,6 +572,26 @@ "internalType": "uint64", "name": "confirmations", "type": "uint64" + }, + { + "internalType": "uint8", + "name": "action", + "type": "uint8" + }, + { + "internalType": "uint16", + "name": "score", + "type": "uint16" + }, + { + "internalType": "uint256", + "name": "reasonMask", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "evidenceHash", + "type": "bytes32" } ], "name": "submitVerification", @@ -376,50 +626,56 @@ "type": "function" } ], - "transactionHash": "0x8457d70969095f12757e7dc1f25562463c9bcfd7625f339f1e101ba1d103cb07", + "transactionHash": "0xb897c97aed2cf995f07b3f73e73dc974585ec757db97162507e9f92a1e4da275", "receipt": { "to": null, - "from": "0x69BD4d7ec258E29d6A9ADD925a543706DBde210c", - "contractAddress": "0x5d5B0c36D1e522C0BB44fdd6402576De42484Ee0", - "transactionIndex": 2, - "gasUsed": "673354", - "logsBloom": "0x00000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000002000020000000001000008000000000000000000000000000000020000000000000000000800000000000000000000000000000000400000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000080000000000000", + "from": "0x8583894d0e57e42abb83039537f314490038efa0", + "contractAddress": "0x497E0962BeD72DC12Fb249995cA618a929C0d17A", + "transactionIndex": 3, + "gasUsed": "933256", + "logsBloom": "0x00000000000000000000000000000000000000000000000000800000000000000000000000000000020000000000000000000002000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000100020000000000000000000800000020000000000000000000000000400000100000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000", "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "transactionHash": "0x8457d70969095f12757e7dc1f25562463c9bcfd7625f339f1e101ba1d103cb07", + "transactionHash": "0xb897c97aed2cf995f07b3f73e73dc974585ec757db97162507e9f92a1e4da275", "logs": [ { - "transactionIndex": 2, - "blockNumber": 42784167, - "transactionHash": "0x8457d70969095f12757e7dc1f25562463c9bcfd7625f339f1e101ba1d103cb07", - "address": "0x5d5B0c36D1e522C0BB44fdd6402576De42484Ee0", + "transactionIndex": 3, + "blockNumber": 44875519, + "transactionHash": "0xb897c97aed2cf995f07b3f73e73dc974585ec757db97162507e9f92a1e4da275", + "address": "0x497E0962BeD72DC12Fb249995cA618a929C0d17A", "topics": [ "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x00000000000000000000000069bd4d7ec258e29d6a9add925a543706dbde210c" + "0x0000000000000000000000008583894d0e57e42abb83039537f314490038efa0" ], "data": "0x", - "logIndex": 1, + "logIndex": 8, "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000" } ], - "blockNumber": 42784167, - "cumulativeGasUsed": "770601", + "blockNumber": 44875519, + "cumulativeGasUsed": "1443596", "status": 1, "byzantium": true }, "args": [ - "0x69BD4d7ec258E29d6A9ADD925a543706DBde210c", - "0x69BD4d7ec258E29d6A9ADD925a543706DBde210c", + "0x8583894d0e57e42abb83039537f314490038efa0", + "0x01D24AE2cD8ad18472BD00AfE4ec425E800e184d", + "0xC1868e054425D378095A003EcbA3823a5D0135C9", "0x12523de19dc41c91F7d2093E0CFbB76b17012C8d", "50000000000000" ], - "numDeployments": 1, - "solcInputHash": "072b17b3dad771bc29c928d7d4532ec5", - "metadata": "{\"compiler\":{\"version\":\"0.8.22+commit.4fc1097e\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_operator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_receiveUln\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_fee\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"NotOperator\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"OwnableInvalidOwner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"OwnableUnauthorizedAccount\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"fee\",\"type\":\"uint256\"}],\"name\":\"FeeSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"payloadHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"confirmations\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"JobAssigned\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"}],\"name\":\"OperatorSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"receiveUln\",\"type\":\"address\"}],\"name\":\"ReceiveUlnSet\",\"type\":\"event\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"packetHeader\",\"type\":\"bytes\"},{\"internalType\":\"bytes32\",\"name\":\"payloadHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"confirmations\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"internalType\":\"struct ILayerZeroDVN.AssignJobParam\",\"name\":\"_param\",\"type\":\"tuple\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"name\":\"assignJob\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"fee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"},{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"name\":\"getFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"operator\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"receiveUln\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_fee\",\"type\":\"uint256\"}],\"name\":\"setFee\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_operator\",\"type\":\"address\"}],\"name\":\"setOperator\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_receiveUln\",\"type\":\"address\"}],\"name\":\"setReceiveUln\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"packetHeader\",\"type\":\"bytes\"},{\"internalType\":\"bytes32\",\"name\":\"payloadHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"confirmations\",\"type\":\"uint64\"}],\"name\":\"submitVerification\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address payable\",\"name\":\"_to\",\"type\":\"address\"}],\"name\":\"withdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"errors\":{\"OwnableInvalidOwner(address)\":[{\"details\":\"The owner is not a valid owner account. (eg. `address(0)`)\"}],\"OwnableUnauthorizedAccount(address)\":[{\"details\":\"The caller account is not authorized to perform an operation.\"}]},\"kind\":\"dev\",\"methods\":{\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner.\"},\"transferOwnership(address)\":{\"details\":\"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.\"}},\"title\":\"ComplianceDVN\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"notice\":\"Thin LayerZero V2 DVN. All compliance judgment is off-chain; the on-chain contract only conforms to the worker-job interface and gates the destination attestation behind an operator key. Withholding `submitVerification` IS the veto.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/ComplianceDVN.sol\":\"ComplianceDVN\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/ILayerZeroDVN.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\ninterface ILayerZeroDVN {\\n struct AssignJobParam {\\n uint32 dstEid;\\n bytes packetHeader;\\n bytes32 payloadHash;\\n uint64 confirmations;\\n address sender;\\n }\\n\\n // @notice query price and assign jobs at the same time\\n // @param _dstEid - the destination endpoint identifier\\n // @param _packetHeader - version + nonce + path\\n // @param _payloadHash - hash of guid + message\\n // @param _confirmations - block confirmation delay before relaying blocks\\n // @param _sender - the source sending contract address\\n // @param _options - options\\n function assignJob(AssignJobParam calldata _param, bytes calldata _options) external payable returns (uint256 fee);\\n\\n // @notice query the dvn fee for relaying block information to the destination chain\\n // @param _dstEid the destination endpoint identifier\\n // @param _confirmations - block confirmation delay before relaying blocks\\n // @param _sender - the source sending contract address\\n // @param _options - options\\n function getFee(\\n uint32 _dstEid,\\n uint64 _confirmations,\\n address _sender,\\n bytes calldata _options\\n ) external view returns (uint256 fee);\\n}\\n\",\"keccak256\":\"0x308e77078242fd5c5746ec29c12e618249134f9e4377c0028ab8f59c07a6014b\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/IReceiveUlnE2.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\n/// @dev should be implemented by the ReceiveUln302 contract and future ReceiveUln contracts on EndpointV2\\ninterface IReceiveUlnE2 {\\n /// @notice for each dvn to verify the payload\\n /// @dev this function signature 0x0223536e\\n function verify(bytes calldata _packetHeader, bytes32 _payloadHash, uint64 _confirmations) external;\\n\\n /// @notice verify the payload at endpoint, will check if all DVNs verified\\n function commitVerification(bytes calldata _packetHeader, bytes32 _payloadHash) external;\\n}\\n\",\"keccak256\":\"0xcdf7e690e5d5c0a3ec26a0d7b1a7fe49c7d16a3634721c3944f77d13ff5d4a91\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/Ownable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\\n\\npragma solidity ^0.8.20;\\n\\nimport {Context} from \\\"../utils/Context.sol\\\";\\n\\n/**\\n * @dev Contract module which provides a basic access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * The initial owner is set to the address provided by the deployer. This can\\n * later be changed with {transferOwnership}.\\n *\\n * This module is used through inheritance. It will make available the modifier\\n * `onlyOwner`, which can be applied to your functions to restrict their use to\\n * the owner.\\n */\\nabstract contract Ownable is Context {\\n address private _owner;\\n\\n /**\\n * @dev The caller account is not authorized to perform an operation.\\n */\\n error OwnableUnauthorizedAccount(address account);\\n\\n /**\\n * @dev The owner is not a valid owner account. (eg. `address(0)`)\\n */\\n error OwnableInvalidOwner(address owner);\\n\\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\\n\\n /**\\n * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\\n */\\n constructor(address initialOwner) {\\n if (initialOwner == address(0)) {\\n revert OwnableInvalidOwner(address(0));\\n }\\n _transferOwnership(initialOwner);\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the owner.\\n */\\n modifier onlyOwner() {\\n _checkOwner();\\n _;\\n }\\n\\n /**\\n * @dev Returns the address of the current owner.\\n */\\n function owner() public view virtual returns (address) {\\n return _owner;\\n }\\n\\n /**\\n * @dev Throws if the sender is not the owner.\\n */\\n function _checkOwner() internal view virtual {\\n if (owner() != _msgSender()) {\\n revert OwnableUnauthorizedAccount(_msgSender());\\n }\\n }\\n\\n /**\\n * @dev Leaves the contract without owner. It will not be possible to call\\n * `onlyOwner` functions. Can only be called by the current owner.\\n *\\n * NOTE: Renouncing ownership will leave the contract without an owner,\\n * thereby disabling any functionality that is only available to the owner.\\n */\\n function renounceOwnership() public virtual onlyOwner {\\n _transferOwnership(address(0));\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual onlyOwner {\\n if (newOwner == address(0)) {\\n revert OwnableInvalidOwner(address(0));\\n }\\n _transferOwnership(newOwner);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Internal function without access restriction.\\n */\\n function _transferOwnership(address newOwner) internal virtual {\\n address oldOwner = _owner;\\n _owner = newOwner;\\n emit OwnershipTransferred(oldOwner, newOwner);\\n }\\n}\\n\",\"keccak256\":\"0xff6d0bb2e285473e5311d9d3caacb525ae3538a80758c10649a4d61029b017bb\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Context.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\\n\\npragma solidity ^0.8.20;\\n\\n/**\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract Context {\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n\\n function _contextSuffixLength() internal view virtual returns (uint256) {\\n return 0;\\n }\\n}\\n\",\"keccak256\":\"0x493033a8d1b176a037b2cc6a04dad01a5c157722049bbecf632ca876224dd4b2\",\"license\":\"MIT\"},\"contracts/ComplianceDVN.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.22;\\n\\nimport { Ownable } from \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\nimport { ILayerZeroDVN } from \\\"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/ILayerZeroDVN.sol\\\";\\nimport { IReceiveUlnE2 } from \\\"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/IReceiveUlnE2.sol\\\";\\n\\n/// @title ComplianceDVN\\n/// @notice Thin LayerZero V2 DVN. All compliance judgment is off-chain; the on-chain\\n/// contract only conforms to the worker-job interface and gates the destination\\n/// attestation behind an operator key. Withholding `submitVerification` IS the veto.\\ncontract ComplianceDVN is ILayerZeroDVN, Ownable {\\n address public operator; // off-chain worker key\\n address public receiveUln; // ReceiveUln302 on this chain\\n uint256 public fee;\\n\\n event JobAssigned(uint32 dstEid, bytes32 payloadHash, uint64 confirmations, address sender);\\n event OperatorSet(address operator);\\n event ReceiveUlnSet(address receiveUln);\\n event FeeSet(uint256 fee);\\n\\n error NotOperator();\\n\\n modifier onlyOperator() {\\n if (msg.sender != operator) revert NotOperator();\\n _;\\n }\\n\\n constructor(address _owner, address _operator, address _receiveUln, uint256 _fee) Ownable(_owner) {\\n require(_operator != address(0), \\\"zero operator\\\");\\n require(_receiveUln != address(0), \\\"zero receiveUln\\\");\\n operator = _operator;\\n receiveUln = _receiveUln;\\n fee = _fee;\\n }\\n\\n function getFee(\\n uint32 /*_dstEid*/,\\n uint64 /*_confirmations*/,\\n address /*_sender*/,\\n bytes calldata /*_options*/\\n ) external view returns (uint256) {\\n return fee;\\n }\\n\\n function assignJob(AssignJobParam calldata _param, bytes calldata) external payable returns (uint256) {\\n // NOTE: SendUln302 calls assignJob WITHOUT forwarding value (msg.value == 0); the\\n // messagelib accrues each worker's fee internally and workers withdraw separately\\n // (see SendUlnBase._assignJobs). So we must NOT require msg.value >= fee here \\u2014 doing\\n // so reverts every real send. We simply record the job and return our fee quote.\\n emit JobAssigned(_param.dstEid, _param.payloadHash, _param.confirmations, _param.sender);\\n return fee;\\n }\\n\\n function submitVerification(\\n bytes calldata packetHeader,\\n bytes32 payloadHash,\\n uint64 confirmations\\n ) external onlyOperator {\\n IReceiveUlnE2(receiveUln).verify(packetHeader, payloadHash, confirmations);\\n }\\n\\n function setOperator(address _operator) external onlyOwner {\\n require(_operator != address(0), \\\"zero operator\\\");\\n operator = _operator;\\n emit OperatorSet(_operator);\\n }\\n\\n function setReceiveUln(address _receiveUln) external onlyOwner {\\n require(_receiveUln != address(0), \\\"zero receiveUln\\\");\\n receiveUln = _receiveUln;\\n emit ReceiveUlnSet(_receiveUln);\\n }\\n\\n function setFee(uint256 _fee) external onlyOwner {\\n fee = _fee;\\n emit FeeSet(_fee);\\n }\\n\\n function withdraw(address payable _to) external onlyOwner {\\n (bool ok, ) = _to.call{ value: address(this).balance }(\\\"\\\");\\n require(ok, \\\"withdraw failed\\\");\\n }\\n}\\n\",\"keccak256\":\"0x335c7811fc779c745ce8b7c8e268d3b4ff6f7625ee0ef4aa8ca8040f8c40314b\",\"license\":\"MIT\"}},\"version\":1}", - "bytecode": "0x608060405234801561001057600080fd5b50604051610b61380380610b6183398101604081905261002f9161019c565b836001600160a01b03811661005f57604051631e4fbdf760e01b8152600060048201526024015b60405180910390fd5b61006881610130565b506001600160a01b0383166100af5760405162461bcd60e51b815260206004820152600d60248201526c3d32b9379037b832b930ba37b960991b6044820152606401610056565b6001600160a01b0382166100f75760405162461bcd60e51b815260206004820152600f60248201526e3d32b937903932b1b2b4bb32aab63760891b6044820152606401610056565b600180546001600160a01b039485166001600160a01b0319918216179091556002805493909416921691909117909155600355506101e7565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b80516001600160a01b038116811461019757600080fd5b919050565b600080600080608085870312156101b257600080fd5b6101bb85610180565b93506101c960208601610180565b92506101d760408601610180565b6060959095015193969295505050565b61096b806101f66000396000f3fe6080604052600436106100c25760003560e01c80638da5cb5b1161007f578063ddca3f4311610059578063ddca3f4314610201578063e3b6f56714610217578063e49ca47114610237578063f2fde38b1461025757600080fd5b80638da5cb5b146101b057806395d376d7146101ce578063b3ab15fb146101e157600080fd5b806330bb3aac146100c757806348c8e2d61461010157806351cff8d914610123578063570ca7351461014357806369fe0e2d1461017b578063715018a61461019b575b600080fd5b3480156100d357600080fd5b506100ee6100e2366004610732565b60035495945050505050565b6040519081526020015b60405180910390f35b34801561010d57600080fd5b5061012161011c3660046107aa565b610277565b005b34801561012f57600080fd5b5061012161013e366004610807565b610310565b34801561014f57600080fd5b50600154610163906001600160a01b031681565b6040516001600160a01b0390911681526020016100f8565b34801561018757600080fd5b5061012161019636600461082b565b6103b6565b3480156101a757600080fd5b506101216103fa565b3480156101bc57600080fd5b506000546001600160a01b0316610163565b6100ee6101dc366004610844565b61040e565b3480156101ed57600080fd5b506101216101fc366004610807565b6104ae565b34801561020d57600080fd5b506100ee60035481565b34801561022357600080fd5b50610121610232366004610807565b61054a565b34801561024357600080fd5b50600254610163906001600160a01b031681565b34801561026357600080fd5b50610121610272366004610807565b6105e8565b6001546001600160a01b031633146102a257604051631f0853c160e21b815260040160405180910390fd5b600254604051630111a9b760e11b81526001600160a01b0390911690630223536e906102d89087908790879087906004016108b5565b600060405180830381600087803b1580156102f257600080fd5b505af1158015610306573d6000803e3d6000fd5b5050505050505050565b610318610626565b6000816001600160a01b03164760405160006040518083038185875af1925050503d8060008114610365576040519150601f19603f3d011682016040523d82523d6000602084013e61036a565b606091505b50509050806103b25760405162461bcd60e51b815260206004820152600f60248201526e1dda5d1a191c985dc819985a5b1959608a1b60448201526064015b60405180910390fd5b5050565b6103be610626565b60038190556040518181527f20461e09b8e557b77e107939f9ce6544698123aad0fc964ac5cc59b7df2e608f906020015b60405180910390a150565b610402610626565b61040c6000610653565b565b60007ff266d2fc560c9aaf31e6bdf020cac0f2a9d5b3aa16f932e16434749ddff1499961043e60208601866108ff565b6040860135610453608088016060890161091a565b61046360a0890160808a01610807565b6040805163ffffffff959095168552602085019390935267ffffffffffffffff91909116838301526001600160a01b03166060830152519081900360800190a1506003549392505050565b6104b6610626565b6001600160a01b0381166104fc5760405162461bcd60e51b815260206004820152600d60248201526c3d32b9379037b832b930ba37b960991b60448201526064016103a9565b600180546001600160a01b0319166001600160a01b0383169081179091556040519081527f99d737e0adf2c449d71890b86772885ec7959b152ddb265f76325b6e68e105d3906020016103ef565b610552610626565b6001600160a01b03811661059a5760405162461bcd60e51b815260206004820152600f60248201526e3d32b937903932b1b2b4bb32aab63760891b60448201526064016103a9565b600280546001600160a01b0319166001600160a01b0383169081179091556040519081527f549e7b08606f710537aa4c9f00cb4d403d0048a8049411f3c1922d6e97744d6a906020016103ef565b6105f0610626565b6001600160a01b03811661061a57604051631e4fbdf760e01b8152600060048201526024016103a9565b61062381610653565b50565b6000546001600160a01b0316331461040c5760405163118cdaa760e01b81523360048201526024016103a9565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b803563ffffffff811681146106b757600080fd5b919050565b803567ffffffffffffffff811681146106b757600080fd5b6001600160a01b038116811461062357600080fd5b60008083601f8401126106fb57600080fd5b50813567ffffffffffffffff81111561071357600080fd5b60208301915083602082850101111561072b57600080fd5b9250929050565b60008060008060006080868803121561074a57600080fd5b610753866106a3565b9450610761602087016106bc565b93506040860135610771816106d4565b9250606086013567ffffffffffffffff81111561078d57600080fd5b610799888289016106e9565b969995985093965092949392505050565b600080600080606085870312156107c057600080fd5b843567ffffffffffffffff8111156107d757600080fd5b6107e3878288016106e9565b909550935050602085013591506107fc604086016106bc565b905092959194509250565b60006020828403121561081957600080fd5b8135610824816106d4565b9392505050565b60006020828403121561083d57600080fd5b5035919050565b60008060006040848603121561085957600080fd5b833567ffffffffffffffff8082111561087157600080fd5b9085019060a0828803121561088557600080fd5b9093506020850135908082111561089b57600080fd5b506108a8868287016106e9565b9497909650939450505050565b606081528360608201528385608083013760006080858301015260006080601f19601f870116830101905083602083015267ffffffffffffffff8316604083015295945050505050565b60006020828403121561091157600080fd5b610824826106a3565b60006020828403121561092c57600080fd5b610824826106bc56fea26469706673582212200429577ba4787f70d95d6de684082e4a59ee8692b228009a92be0dbe0d2d237164736f6c63430008160033", - "deployedBytecode": "0x6080604052600436106100c25760003560e01c80638da5cb5b1161007f578063ddca3f4311610059578063ddca3f4314610201578063e3b6f56714610217578063e49ca47114610237578063f2fde38b1461025757600080fd5b80638da5cb5b146101b057806395d376d7146101ce578063b3ab15fb146101e157600080fd5b806330bb3aac146100c757806348c8e2d61461010157806351cff8d914610123578063570ca7351461014357806369fe0e2d1461017b578063715018a61461019b575b600080fd5b3480156100d357600080fd5b506100ee6100e2366004610732565b60035495945050505050565b6040519081526020015b60405180910390f35b34801561010d57600080fd5b5061012161011c3660046107aa565b610277565b005b34801561012f57600080fd5b5061012161013e366004610807565b610310565b34801561014f57600080fd5b50600154610163906001600160a01b031681565b6040516001600160a01b0390911681526020016100f8565b34801561018757600080fd5b5061012161019636600461082b565b6103b6565b3480156101a757600080fd5b506101216103fa565b3480156101bc57600080fd5b506000546001600160a01b0316610163565b6100ee6101dc366004610844565b61040e565b3480156101ed57600080fd5b506101216101fc366004610807565b6104ae565b34801561020d57600080fd5b506100ee60035481565b34801561022357600080fd5b50610121610232366004610807565b61054a565b34801561024357600080fd5b50600254610163906001600160a01b031681565b34801561026357600080fd5b50610121610272366004610807565b6105e8565b6001546001600160a01b031633146102a257604051631f0853c160e21b815260040160405180910390fd5b600254604051630111a9b760e11b81526001600160a01b0390911690630223536e906102d89087908790879087906004016108b5565b600060405180830381600087803b1580156102f257600080fd5b505af1158015610306573d6000803e3d6000fd5b5050505050505050565b610318610626565b6000816001600160a01b03164760405160006040518083038185875af1925050503d8060008114610365576040519150601f19603f3d011682016040523d82523d6000602084013e61036a565b606091505b50509050806103b25760405162461bcd60e51b815260206004820152600f60248201526e1dda5d1a191c985dc819985a5b1959608a1b60448201526064015b60405180910390fd5b5050565b6103be610626565b60038190556040518181527f20461e09b8e557b77e107939f9ce6544698123aad0fc964ac5cc59b7df2e608f906020015b60405180910390a150565b610402610626565b61040c6000610653565b565b60007ff266d2fc560c9aaf31e6bdf020cac0f2a9d5b3aa16f932e16434749ddff1499961043e60208601866108ff565b6040860135610453608088016060890161091a565b61046360a0890160808a01610807565b6040805163ffffffff959095168552602085019390935267ffffffffffffffff91909116838301526001600160a01b03166060830152519081900360800190a1506003549392505050565b6104b6610626565b6001600160a01b0381166104fc5760405162461bcd60e51b815260206004820152600d60248201526c3d32b9379037b832b930ba37b960991b60448201526064016103a9565b600180546001600160a01b0319166001600160a01b0383169081179091556040519081527f99d737e0adf2c449d71890b86772885ec7959b152ddb265f76325b6e68e105d3906020016103ef565b610552610626565b6001600160a01b03811661059a5760405162461bcd60e51b815260206004820152600f60248201526e3d32b937903932b1b2b4bb32aab63760891b60448201526064016103a9565b600280546001600160a01b0319166001600160a01b0383169081179091556040519081527f549e7b08606f710537aa4c9f00cb4d403d0048a8049411f3c1922d6e97744d6a906020016103ef565b6105f0610626565b6001600160a01b03811661061a57604051631e4fbdf760e01b8152600060048201526024016103a9565b61062381610653565b50565b6000546001600160a01b0316331461040c5760405163118cdaa760e01b81523360048201526024016103a9565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b803563ffffffff811681146106b757600080fd5b919050565b803567ffffffffffffffff811681146106b757600080fd5b6001600160a01b038116811461062357600080fd5b60008083601f8401126106fb57600080fd5b50813567ffffffffffffffff81111561071357600080fd5b60208301915083602082850101111561072b57600080fd5b9250929050565b60008060008060006080868803121561074a57600080fd5b610753866106a3565b9450610761602087016106bc565b93506040860135610771816106d4565b9250606086013567ffffffffffffffff81111561078d57600080fd5b610799888289016106e9565b969995985093965092949392505050565b600080600080606085870312156107c057600080fd5b843567ffffffffffffffff8111156107d757600080fd5b6107e3878288016106e9565b909550935050602085013591506107fc604086016106bc565b905092959194509250565b60006020828403121561081957600080fd5b8135610824816106d4565b9392505050565b60006020828403121561083d57600080fd5b5035919050565b60008060006040848603121561085957600080fd5b833567ffffffffffffffff8082111561087157600080fd5b9085019060a0828803121561088557600080fd5b9093506020850135908082111561089b57600080fd5b506108a8868287016106e9565b9497909650939450505050565b606081528360608201528385608083013760006080858301015260006080601f19601f870116830101905083602083015267ffffffffffffffff8316604083015295945050505050565b60006020828403121561091157600080fd5b610824826106a3565b60006020828403121561092c57600080fd5b610824826106bc56fea26469706673582212200429577ba4787f70d95d6de684082e4a59ee8692b228009a92be0dbe0d2d237164736f6c63430008160033", + "numDeployments": 3, + "solcInputHash": "97ae1cbcc67ee4ffae50031ebd6ec920", + "metadata": "{\"compiler\":{\"version\":\"0.8.22+commit.4fc1097e\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_operator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_sendUln\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_receiveUln\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_fee\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"AllowNotSeparatelyRecorded\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotOperator\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotSendLibrary\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"OwnableInvalidOwner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"OwnableUnauthorizedAccount\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"action\",\"type\":\"uint8\"}],\"name\":\"UnknownAction\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"action\",\"type\":\"uint8\"}],\"name\":\"VerificationRequiresAllow\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"fee\",\"type\":\"uint256\"}],\"name\":\"FeeSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"payloadHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"confirmations\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"JobAssigned\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"}],\"name\":\"OperatorSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"payloadHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"approver\",\"type\":\"address\"}],\"name\":\"PacketApproved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"receiveUln\",\"type\":\"address\"}],\"name\":\"ReceiveUlnSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"payloadHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"action\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"score\",\"type\":\"uint16\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"reasonMask\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"evidenceHash\",\"type\":\"bytes32\"}],\"name\":\"RiskVerdict\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"sendUln\",\"type\":\"address\"}],\"name\":\"SendUlnSet\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"ACTION_ALLOW\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"ACTION_BLOCK\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"ACTION_DELAY\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"ACTION_MANUAL_REVIEW\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"payloadHash\",\"type\":\"bytes32\"}],\"name\":\"approvePacket\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"packetHeader\",\"type\":\"bytes\"},{\"internalType\":\"bytes32\",\"name\":\"payloadHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"confirmations\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"internalType\":\"struct ILayerZeroDVN.AssignJobParam\",\"name\":\"_param\",\"type\":\"tuple\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"name\":\"assignJob\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"fee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"},{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"name\":\"getFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"operator\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"receiveUln\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"payloadHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint8\",\"name\":\"action\",\"type\":\"uint8\"},{\"internalType\":\"uint16\",\"name\":\"score\",\"type\":\"uint16\"},{\"internalType\":\"uint256\",\"name\":\"reasonMask\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"evidenceHash\",\"type\":\"bytes32\"}],\"name\":\"recordVerdict\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"sendUln\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_fee\",\"type\":\"uint256\"}],\"name\":\"setFee\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_operator\",\"type\":\"address\"}],\"name\":\"setOperator\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_receiveUln\",\"type\":\"address\"}],\"name\":\"setReceiveUln\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_sendUln\",\"type\":\"address\"}],\"name\":\"setSendUln\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"packetHeader\",\"type\":\"bytes\"},{\"internalType\":\"bytes32\",\"name\":\"payloadHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"confirmations\",\"type\":\"uint64\"},{\"internalType\":\"uint8\",\"name\":\"action\",\"type\":\"uint8\"},{\"internalType\":\"uint16\",\"name\":\"score\",\"type\":\"uint16\"},{\"internalType\":\"uint256\",\"name\":\"reasonMask\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"evidenceHash\",\"type\":\"bytes32\"}],\"name\":\"submitVerification\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address payable\",\"name\":\"_to\",\"type\":\"address\"}],\"name\":\"withdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"errors\":{\"AllowNotSeparatelyRecorded()\":[{\"details\":\"An allow rides along on `submitVerification`, so recording one separately would double-report the same outcome.\"}],\"OwnableInvalidOwner(address)\":[{\"details\":\"The owner is not a valid owner account. (eg. `address(0)`)\"}],\"OwnableUnauthorizedAccount(address)\":[{\"details\":\"The caller account is not authorized to perform an operation.\"}],\"VerificationRequiresAllow(uint8)\":[{\"details\":\"Submitting a verification asserts the packet was allowed; any other action would be a self-contradicting record.\"}]},\"events\":{\"RiskVerdict(bytes32,uint8,uint16,uint256,bytes32)\":{\"params\":{\"action\":\"ACTION_* below\",\"evidenceHash\":\"keccak256 of the canonical evidence document held off-chain\",\"payloadHash\":\"the packet this verdict is about\",\"reasonMask\":\"bitmask of reason codes; bit assignments are append-only and documented in the worker's `assess/verdict.ts`\",\"score\":\"0-100 risk score the action was derived from\"}}},\"kind\":\"dev\",\"methods\":{\"approvePacket(bytes32)\":{\"details\":\"Emits only; no storage. The worker observes `PacketApproved` and releases the packet from its local deferred queue. Approval is a human override of a risk verdict, so it is separated from the operator key by design \\u2014 a compromised or buggy worker cannot approve the packets it chose to hold.\"},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"recordVerdict(bytes32,uint8,uint16,uint256,bytes32)\":{\"details\":\"Withholding the attestation is what actually stops the packet; this only leaves the audit trail. It is therefore best-effort by design \\u2014 the worker treats a failure here as a lost record, never as a failure to enforce.\"},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner.\"},\"submitVerification(bytes,bytes32,uint64,uint8,uint16,uint256,bytes32)\":{\"details\":\"The verdict rides along at no extra transaction cost, so an allowed packet always carries an auditable reason for having been allowed. `action` must be ACTION_ALLOW: a packet that was blocked or held cannot also have been verified. An owner-approved release is reported as ACTION_ALLOW too \\u2014 a human allowed it \\u2014 with the reason mask still carrying why it had been held.\"},\"transferOwnership(address)\":{\"details\":\"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.\"}},\"stateVariables\":{\"ACTION_ALLOW\":{\"details\":\"Action codes. These are part of the event ABI: an indexer decoding old logs relies on them, so the numbering is permanent. Kept in sync with the worker's ACTION_CODES.\"}},\"title\":\"ComplianceDVN\",\"version\":1},\"userdoc\":{\"events\":{\"PacketApproved(bytes32,address)\":{\"notice\":\"A held packet cleared for verification by the owner. Deliberately owner-only: the worker holds only the operator key, so it cannot approve its own holds.\"},\"RiskVerdict(bytes32,uint8,uint16,uint256,bytes32)\":{\"notice\":\"The risk decision behind a packet's outcome.\"}},\"kind\":\"user\",\"methods\":{\"approvePacket(bytes32)\":{\"notice\":\"Clear a packet the worker withheld for manual review.\"},\"recordVerdict(bytes32,uint8,uint16,uint256,bytes32)\":{\"notice\":\"Record a verdict for a packet that was NOT verified.\"},\"submitVerification(bytes,bytes32,uint64,uint8,uint16,uint256,bytes32)\":{\"notice\":\"Attest a packet and record the risk verdict that permitted it, in one call.\"}},\"notice\":\"Thin LayerZero V2 DVN. All compliance judgment is off-chain; the on-chain contract only conforms to the worker-job interface and gates the destination attestation behind an operator key. Withholding `submitVerification` IS the veto.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/ComplianceDVN.sol\":\"ComplianceDVN\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/ILayerZeroDVN.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\ninterface ILayerZeroDVN {\\n struct AssignJobParam {\\n uint32 dstEid;\\n bytes packetHeader;\\n bytes32 payloadHash;\\n uint64 confirmations;\\n address sender;\\n }\\n\\n // @notice query price and assign jobs at the same time\\n // @param _dstEid - the destination endpoint identifier\\n // @param _packetHeader - version + nonce + path\\n // @param _payloadHash - hash of guid + message\\n // @param _confirmations - block confirmation delay before relaying blocks\\n // @param _sender - the source sending contract address\\n // @param _options - options\\n function assignJob(AssignJobParam calldata _param, bytes calldata _options) external payable returns (uint256 fee);\\n\\n // @notice query the dvn fee for relaying block information to the destination chain\\n // @param _dstEid the destination endpoint identifier\\n // @param _confirmations - block confirmation delay before relaying blocks\\n // @param _sender - the source sending contract address\\n // @param _options - options\\n function getFee(\\n uint32 _dstEid,\\n uint64 _confirmations,\\n address _sender,\\n bytes calldata _options\\n ) external view returns (uint256 fee);\\n}\\n\",\"keccak256\":\"0x308e77078242fd5c5746ec29c12e618249134f9e4377c0028ab8f59c07a6014b\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/IReceiveUlnE2.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\n/// @dev should be implemented by the ReceiveUln302 contract and future ReceiveUln contracts on EndpointV2\\ninterface IReceiveUlnE2 {\\n /// @notice for each dvn to verify the payload\\n /// @dev this function signature 0x0223536e\\n function verify(bytes calldata _packetHeader, bytes32 _payloadHash, uint64 _confirmations) external;\\n\\n /// @notice verify the payload at endpoint, will check if all DVNs verified\\n function commitVerification(bytes calldata _packetHeader, bytes32 _payloadHash) external;\\n}\\n\",\"keccak256\":\"0xcdf7e690e5d5c0a3ec26a0d7b1a7fe49c7d16a3634721c3944f77d13ff5d4a91\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/Ownable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\\n\\npragma solidity ^0.8.20;\\n\\nimport {Context} from \\\"../utils/Context.sol\\\";\\n\\n/**\\n * @dev Contract module which provides a basic access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * The initial owner is set to the address provided by the deployer. This can\\n * later be changed with {transferOwnership}.\\n *\\n * This module is used through inheritance. It will make available the modifier\\n * `onlyOwner`, which can be applied to your functions to restrict their use to\\n * the owner.\\n */\\nabstract contract Ownable is Context {\\n address private _owner;\\n\\n /**\\n * @dev The caller account is not authorized to perform an operation.\\n */\\n error OwnableUnauthorizedAccount(address account);\\n\\n /**\\n * @dev The owner is not a valid owner account. (eg. `address(0)`)\\n */\\n error OwnableInvalidOwner(address owner);\\n\\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\\n\\n /**\\n * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\\n */\\n constructor(address initialOwner) {\\n if (initialOwner == address(0)) {\\n revert OwnableInvalidOwner(address(0));\\n }\\n _transferOwnership(initialOwner);\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the owner.\\n */\\n modifier onlyOwner() {\\n _checkOwner();\\n _;\\n }\\n\\n /**\\n * @dev Returns the address of the current owner.\\n */\\n function owner() public view virtual returns (address) {\\n return _owner;\\n }\\n\\n /**\\n * @dev Throws if the sender is not the owner.\\n */\\n function _checkOwner() internal view virtual {\\n if (owner() != _msgSender()) {\\n revert OwnableUnauthorizedAccount(_msgSender());\\n }\\n }\\n\\n /**\\n * @dev Leaves the contract without owner. It will not be possible to call\\n * `onlyOwner` functions. Can only be called by the current owner.\\n *\\n * NOTE: Renouncing ownership will leave the contract without an owner,\\n * thereby disabling any functionality that is only available to the owner.\\n */\\n function renounceOwnership() public virtual onlyOwner {\\n _transferOwnership(address(0));\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual onlyOwner {\\n if (newOwner == address(0)) {\\n revert OwnableInvalidOwner(address(0));\\n }\\n _transferOwnership(newOwner);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Internal function without access restriction.\\n */\\n function _transferOwnership(address newOwner) internal virtual {\\n address oldOwner = _owner;\\n _owner = newOwner;\\n emit OwnershipTransferred(oldOwner, newOwner);\\n }\\n}\\n\",\"keccak256\":\"0xff6d0bb2e285473e5311d9d3caacb525ae3538a80758c10649a4d61029b017bb\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Context.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\\n\\npragma solidity ^0.8.20;\\n\\n/**\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract Context {\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n\\n function _contextSuffixLength() internal view virtual returns (uint256) {\\n return 0;\\n }\\n}\\n\",\"keccak256\":\"0x493033a8d1b176a037b2cc6a04dad01a5c157722049bbecf632ca876224dd4b2\",\"license\":\"MIT\"},\"contracts/ComplianceDVN.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\r\\npragma solidity ^0.8.22;\\r\\n\\r\\nimport { Ownable } from \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\r\\nimport { ILayerZeroDVN } from \\\"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/ILayerZeroDVN.sol\\\";\\r\\nimport { IReceiveUlnE2 } from \\\"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/IReceiveUlnE2.sol\\\";\\r\\n\\r\\n/// @title ComplianceDVN\\r\\n/// @notice Thin LayerZero V2 DVN. All compliance judgment is off-chain; the on-chain\\r\\n/// contract only conforms to the worker-job interface and gates the destination\\r\\n/// attestation behind an operator key. Withholding `submitVerification` IS the veto.\\r\\ncontract ComplianceDVN is ILayerZeroDVN, Ownable {\\r\\n address public operator; // off-chain worker key\\r\\n address public sendUln; // SendUln302 on this chain \\u2014 the only address allowed to assign jobs\\r\\n address public receiveUln; // ReceiveUln302 on this chain\\r\\n uint256 public fee;\\r\\n\\r\\n event JobAssigned(uint32 dstEid, bytes32 payloadHash, uint64 confirmations, address sender);\\r\\n event OperatorSet(address operator);\\r\\n event SendUlnSet(address sendUln);\\r\\n event ReceiveUlnSet(address receiveUln);\\r\\n event FeeSet(uint256 fee);\\r\\n\\r\\n /// @notice A held packet cleared for verification by the owner. Deliberately owner-only:\\r\\n /// the worker holds only the operator key, so it cannot approve its own holds.\\r\\n event PacketApproved(bytes32 indexed payloadHash, address approver);\\r\\n\\r\\n /// @notice The risk decision behind a packet's outcome.\\r\\n /// @param payloadHash the packet this verdict is about\\r\\n /// @param action ACTION_* below\\r\\n /// @param score 0-100 risk score the action was derived from\\r\\n /// @param reasonMask bitmask of reason codes; bit assignments are append-only and\\r\\n /// documented in the worker's `assess/verdict.ts`\\r\\n /// @param evidenceHash keccak256 of the canonical evidence document held off-chain\\r\\n event RiskVerdict(\\r\\n bytes32 indexed payloadHash,\\r\\n uint8 action,\\r\\n uint16 score,\\r\\n uint256 reasonMask,\\r\\n bytes32 evidenceHash\\r\\n );\\r\\n\\r\\n /// @dev Action codes. These are part of the event ABI: an indexer decoding old logs relies\\r\\n /// on them, so the numbering is permanent. Kept in sync with the worker's ACTION_CODES.\\r\\n uint8 public constant ACTION_ALLOW = 0;\\r\\n uint8 public constant ACTION_DELAY = 1;\\r\\n uint8 public constant ACTION_MANUAL_REVIEW = 2;\\r\\n uint8 public constant ACTION_BLOCK = 3;\\r\\n\\r\\n error NotOperator();\\r\\n error NotSendLibrary();\\r\\n error UnknownAction(uint8 action);\\r\\n /// @dev Submitting a verification asserts the packet was allowed; any other action would be\\r\\n /// a self-contradicting record.\\r\\n error VerificationRequiresAllow(uint8 action);\\r\\n /// @dev An allow rides along on `submitVerification`, so recording one separately would\\r\\n /// double-report the same outcome.\\r\\n error AllowNotSeparatelyRecorded();\\r\\n\\r\\n modifier onlyOperator() {\\r\\n if (msg.sender != operator) revert NotOperator();\\r\\n _;\\r\\n }\\r\\n\\r\\n constructor(\\r\\n address _owner,\\r\\n address _operator,\\r\\n address _sendUln,\\r\\n address _receiveUln,\\r\\n uint256 _fee\\r\\n ) Ownable(_owner) {\\r\\n require(_operator != address(0), \\\"zero operator\\\");\\r\\n require(_sendUln != address(0), \\\"zero sendUln\\\");\\r\\n require(_receiveUln != address(0), \\\"zero receiveUln\\\");\\r\\n operator = _operator;\\r\\n sendUln = _sendUln;\\r\\n receiveUln = _receiveUln;\\r\\n fee = _fee;\\r\\n }\\r\\n\\r\\n function getFee(\\r\\n uint32 /*_dstEid*/,\\r\\n uint64 /*_confirmations*/,\\r\\n address /*_sender*/,\\r\\n bytes calldata /*_options*/\\r\\n ) external view returns (uint256) {\\r\\n return fee;\\r\\n }\\r\\n\\r\\n function assignJob(AssignJobParam calldata _param, bytes calldata) external payable returns (uint256) {\\r\\n // Only the send library assigns jobs. The worker treats a JobAssigned payloadHash as\\r\\n // \\\"this packet is ours to screen\\\" and spends operator gas verifying it, so an open\\r\\n // assignJob would let anyone point the worker at packets no one asked it to verify.\\r\\n if (msg.sender != sendUln) revert NotSendLibrary();\\r\\n // NOTE: SendUln302 calls assignJob WITHOUT forwarding value (msg.value == 0); the\\r\\n // messagelib accrues each worker's fee internally and workers withdraw separately\\r\\n // (see SendUlnBase._assignJobs). So we must NOT require msg.value >= fee here \\u2014 doing\\r\\n // so reverts every real send. We simply record the job and return our fee quote.\\r\\n emit JobAssigned(_param.dstEid, _param.payloadHash, _param.confirmations, _param.sender);\\r\\n return fee;\\r\\n }\\r\\n\\r\\n /// @notice Attest a packet and record the risk verdict that permitted it, in one call.\\r\\n /// @dev The verdict rides along at no extra transaction cost, so an allowed packet always\\r\\n /// carries an auditable reason for having been allowed. `action` must be ACTION_ALLOW:\\r\\n /// a packet that was blocked or held cannot also have been verified. An owner-approved\\r\\n /// release is reported as ACTION_ALLOW too \\u2014 a human allowed it \\u2014 with the reason mask\\r\\n /// still carrying why it had been held.\\r\\n function submitVerification(\\r\\n bytes calldata packetHeader,\\r\\n bytes32 payloadHash,\\r\\n uint64 confirmations,\\r\\n uint8 action,\\r\\n uint16 score,\\r\\n uint256 reasonMask,\\r\\n bytes32 evidenceHash\\r\\n ) external onlyOperator {\\r\\n if (action != ACTION_ALLOW) revert VerificationRequiresAllow(action);\\r\\n IReceiveUlnE2(receiveUln).verify(packetHeader, payloadHash, confirmations);\\r\\n emit RiskVerdict(payloadHash, action, score, reasonMask, evidenceHash);\\r\\n }\\r\\n\\r\\n /// @notice Record a verdict for a packet that was NOT verified.\\r\\n /// @dev Withholding the attestation is what actually stops the packet; this only leaves the\\r\\n /// audit trail. It is therefore best-effort by design \\u2014 the worker treats a failure\\r\\n /// here as a lost record, never as a failure to enforce.\\r\\n function recordVerdict(\\r\\n bytes32 payloadHash,\\r\\n uint8 action,\\r\\n uint16 score,\\r\\n uint256 reasonMask,\\r\\n bytes32 evidenceHash\\r\\n ) external onlyOperator {\\r\\n if (action > ACTION_BLOCK) revert UnknownAction(action);\\r\\n if (action == ACTION_ALLOW) revert AllowNotSeparatelyRecorded();\\r\\n emit RiskVerdict(payloadHash, action, score, reasonMask, evidenceHash);\\r\\n }\\r\\n\\r\\n /// @notice Clear a packet the worker withheld for manual review.\\r\\n /// @dev Emits only; no storage. The worker observes `PacketApproved` and releases the\\r\\n /// packet from its local deferred queue. Approval is a human override of a risk\\r\\n /// verdict, so it is separated from the operator key by design \\u2014 a compromised or\\r\\n /// buggy worker cannot approve the packets it chose to hold.\\r\\n function approvePacket(bytes32 payloadHash) external onlyOwner {\\r\\n emit PacketApproved(payloadHash, msg.sender);\\r\\n }\\r\\n\\r\\n function setOperator(address _operator) external onlyOwner {\\r\\n require(_operator != address(0), \\\"zero operator\\\");\\r\\n operator = _operator;\\r\\n emit OperatorSet(_operator);\\r\\n }\\r\\n\\r\\n function setSendUln(address _sendUln) external onlyOwner {\\r\\n require(_sendUln != address(0), \\\"zero sendUln\\\");\\r\\n sendUln = _sendUln;\\r\\n emit SendUlnSet(_sendUln);\\r\\n }\\r\\n\\r\\n function setReceiveUln(address _receiveUln) external onlyOwner {\\r\\n require(_receiveUln != address(0), \\\"zero receiveUln\\\");\\r\\n receiveUln = _receiveUln;\\r\\n emit ReceiveUlnSet(_receiveUln);\\r\\n }\\r\\n\\r\\n function setFee(uint256 _fee) external onlyOwner {\\r\\n fee = _fee;\\r\\n emit FeeSet(_fee);\\r\\n }\\r\\n\\r\\n function withdraw(address payable _to) external onlyOwner {\\r\\n (bool ok, ) = _to.call{ value: address(this).balance }(\\\"\\\");\\r\\n require(ok, \\\"withdraw failed\\\");\\r\\n }\\r\\n}\\r\\n\",\"keccak256\":\"0x87439dd615ec1afef7dbbc323b152defa47f21292bf80d472faf345559b18f5a\",\"license\":\"MIT\"}},\"version\":1}", + "bytecode": "0x60806040523480156200001157600080fd5b5060405162001022380380620010228339810160408190526200003491620001fe565b846001600160a01b0381166200006557604051631e4fbdf760e01b8152600060048201526024015b60405180910390fd5b620000708162000191565b506001600160a01b038416620000b95760405162461bcd60e51b815260206004820152600d60248201526c3d32b9379037b832b930ba37b960991b60448201526064016200005c565b6001600160a01b038316620001005760405162461bcd60e51b815260206004820152600c60248201526b3d32b9379039b2b7322ab63760a11b60448201526064016200005c565b6001600160a01b0382166200014a5760405162461bcd60e51b815260206004820152600f60248201526e3d32b937903932b1b2b4bb32aab63760891b60448201526064016200005c565b600180546001600160a01b03199081166001600160a01b03968716179091556002805482169486169490941790935560038054909316919093161790556004555062000265565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b80516001600160a01b0381168114620001f957600080fd5b919050565b600080600080600060a086880312156200021757600080fd5b6200022286620001e1565b94506200023260208701620001e1565b93506200024260408701620001e1565b92506200025260608701620001e1565b9150608086015190509295509295909350565b610dad80620002756000396000f3fe60806040526004361061012a5760003560e01c80638337a03f116100ab578063a3be73e81161006f578063a3be73e81461031a578063b3ab15fb1461032f578063ddca3f431461034f578063e3b6f56714610365578063e49ca47114610385578063f2fde38b146103a557600080fd5b80638337a03f1461029457806389861756146102b45780638da5cb5b146102c95780638f16020a146102e757806395d376d71461030757600080fd5b8063570ca735116100f2578063570ca7351461020a578063621d665d1461022a57806369fe0e2d1461024a578063715018a61461026a57806376ab3b431461027f57600080fd5b80630b3448f21461012f5780630d54d7071461016c57806330bb3aac1461018e57806337610b1b146101c357806351cff8d9146101ea575b600080fd5b34801561013b57600080fd5b5060025461014f906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561017857600080fd5b5061018c610187366004610ac9565b6103c5565b005b34801561019a57600080fd5b506101b56101a9366004610b83565b60045495945050505050565b604051908152602001610163565b3480156101cf57600080fd5b506101d8600381565b60405160ff9091168152602001610163565b3480156101f657600080fd5b5061018c610205366004610bfb565b6104de565b34801561021657600080fd5b5060015461014f906001600160a01b031681565b34801561023657600080fd5b5061018c610245366004610c1f565b61057f565b34801561025657600080fd5b5061018c610265366004610c1f565b6105bf565b34801561027657600080fd5b5061018c610603565b34801561028b57600080fd5b506101d8600081565b3480156102a057600080fd5b5061018c6102af366004610bfb565b610617565b3480156102c057600080fd5b506101d8600281565b3480156102d557600080fd5b506000546001600160a01b031661014f565b3480156102f357600080fd5b5061018c610302366004610c38565b6106b2565b6101b5610315366004610c86565b61077f565b34801561032657600080fd5b506101d8600181565b34801561033b57600080fd5b5061018c61034a366004610bfb565b61084b565b34801561035b57600080fd5b506101b560045481565b34801561037157600080fd5b5061018c610380366004610bfb565b6108e7565b34801561039157600080fd5b5060035461014f906001600160a01b031681565b3480156103b157600080fd5b5061018c6103c0366004610bfb565b610985565b6001546001600160a01b031633146103f057604051631f0853c160e21b815260040160405180910390fd5b60ff84161561041c5760405163a0940ea960e01b815260ff851660048201526024015b60405180910390fd5b600354604051630111a9b760e11b81526001600160a01b0390911690630223536e90610452908b908b908b908b90600401610cf7565b600060405180830381600087803b15801561046c57600080fd5b505af1158015610480573d6000803e3d6000fd5b50506040805160ff8816815261ffff87166020820152908101859052606081018490528892507f58ec8d75f99798b0f5d8fe567e102c2006f27041b28ccf9c65e27c8ba010f807915060800160405180910390a25050505050505050565b6104e66109c3565b6000816001600160a01b03164760405160006040518083038185875af1925050503d8060008114610533576040519150601f19603f3d011682016040523d82523d6000602084013e610538565b606091505b505090508061057b5760405162461bcd60e51b815260206004820152600f60248201526e1dda5d1a191c985dc819985a5b1959608a1b6044820152606401610413565b5050565b6105876109c3565b60405133815281907feef04c31cb4b8b2e7ade37c9f258844c21380c82fad1b9af9529dc3b1326daba9060200160405180910390a250565b6105c76109c3565b60048190556040518181527f20461e09b8e557b77e107939f9ce6544698123aad0fc964ac5cc59b7df2e608f906020015b60405180910390a150565b61060b6109c3565b61061560006109f0565b565b61061f6109c3565b6001600160a01b0381166106645760405162461bcd60e51b815260206004820152600c60248201526b3d32b9379039b2b7322ab63760a11b6044820152606401610413565b600280546001600160a01b0319166001600160a01b0383169081179091556040519081527f81fee7bca94bea3e3e3560b9e8019e639c980511659ce436bd6e62790dbddbbd906020016105f8565b6001546001600160a01b031633146106dd57604051631f0853c160e21b815260040160405180910390fd5b600360ff85161115610707576040516360df9f8760e01b815260ff85166004820152602401610413565b60ff8416610728576040516330e4fe0360e11b815260040160405180910390fd5b6040805160ff8616815261ffff851660208201529081018390526060810182905285907f58ec8d75f99798b0f5d8fe567e102c2006f27041b28ccf9c65e27c8ba010f8079060800160405180910390a25050505050565b6002546000906001600160a01b031633146107ad57604051637f2e104960e01b815260040160405180910390fd5b7ff266d2fc560c9aaf31e6bdf020cac0f2a9d5b3aa16f932e16434749ddff149996107db6020860186610d41565b60408601356107f06080880160608901610d5c565b61080060a0890160808a01610bfb565b6040805163ffffffff959095168552602085019390935267ffffffffffffffff91909116838301526001600160a01b03166060830152519081900360800190a1506004549392505050565b6108536109c3565b6001600160a01b0381166108995760405162461bcd60e51b815260206004820152600d60248201526c3d32b9379037b832b930ba37b960991b6044820152606401610413565b600180546001600160a01b0319166001600160a01b0383169081179091556040519081527f99d737e0adf2c449d71890b86772885ec7959b152ddb265f76325b6e68e105d3906020016105f8565b6108ef6109c3565b6001600160a01b0381166109375760405162461bcd60e51b815260206004820152600f60248201526e3d32b937903932b1b2b4bb32aab63760891b6044820152606401610413565b600380546001600160a01b0319166001600160a01b0383169081179091556040519081527f549e7b08606f710537aa4c9f00cb4d403d0048a8049411f3c1922d6e97744d6a906020016105f8565b61098d6109c3565b6001600160a01b0381166109b757604051631e4fbdf760e01b815260006004820152602401610413565b6109c0816109f0565b50565b6000546001600160a01b031633146106155760405163118cdaa760e01b8152336004820152602401610413565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60008083601f840112610a5257600080fd5b50813567ffffffffffffffff811115610a6a57600080fd5b602083019150836020828501011115610a8257600080fd5b9250929050565b803567ffffffffffffffff81168114610aa157600080fd5b919050565b803560ff81168114610aa157600080fd5b803561ffff81168114610aa157600080fd5b60008060008060008060008060e0898b031215610ae557600080fd5b883567ffffffffffffffff811115610afc57600080fd5b610b088b828c01610a40565b90995097505060208901359550610b2160408a01610a89565b9450610b2f60608a01610aa6565b9350610b3d60808a01610ab7565b925060a0890135915060c089013590509295985092959890939650565b803563ffffffff81168114610aa157600080fd5b6001600160a01b03811681146109c057600080fd5b600080600080600060808688031215610b9b57600080fd5b610ba486610b5a565b9450610bb260208701610a89565b93506040860135610bc281610b6e565b9250606086013567ffffffffffffffff811115610bde57600080fd5b610bea88828901610a40565b969995985093965092949392505050565b600060208284031215610c0d57600080fd5b8135610c1881610b6e565b9392505050565b600060208284031215610c3157600080fd5b5035919050565b600080600080600060a08688031215610c5057600080fd5b85359450610c6060208701610aa6565b9350610c6e60408701610ab7565b94979396509394606081013594506080013592915050565b600080600060408486031215610c9b57600080fd5b833567ffffffffffffffff80821115610cb357600080fd5b9085019060a08288031215610cc757600080fd5b90935060208501359080821115610cdd57600080fd5b50610cea86828701610a40565b9497909650939450505050565b606081528360608201528385608083013760006080858301015260006080601f19601f870116830101905083602083015267ffffffffffffffff8316604083015295945050505050565b600060208284031215610d5357600080fd5b610c1882610b5a565b600060208284031215610d6e57600080fd5b610c1882610a8956fea2646970667358221220d94d4a34a82171ea20ebdc7b4651dff6411ce021ee5097ce9daee18d8b3512c364736f6c63430008160033", + "deployedBytecode": "0x60806040526004361061012a5760003560e01c80638337a03f116100ab578063a3be73e81161006f578063a3be73e81461031a578063b3ab15fb1461032f578063ddca3f431461034f578063e3b6f56714610365578063e49ca47114610385578063f2fde38b146103a557600080fd5b80638337a03f1461029457806389861756146102b45780638da5cb5b146102c95780638f16020a146102e757806395d376d71461030757600080fd5b8063570ca735116100f2578063570ca7351461020a578063621d665d1461022a57806369fe0e2d1461024a578063715018a61461026a57806376ab3b431461027f57600080fd5b80630b3448f21461012f5780630d54d7071461016c57806330bb3aac1461018e57806337610b1b146101c357806351cff8d9146101ea575b600080fd5b34801561013b57600080fd5b5060025461014f906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561017857600080fd5b5061018c610187366004610ac9565b6103c5565b005b34801561019a57600080fd5b506101b56101a9366004610b83565b60045495945050505050565b604051908152602001610163565b3480156101cf57600080fd5b506101d8600381565b60405160ff9091168152602001610163565b3480156101f657600080fd5b5061018c610205366004610bfb565b6104de565b34801561021657600080fd5b5060015461014f906001600160a01b031681565b34801561023657600080fd5b5061018c610245366004610c1f565b61057f565b34801561025657600080fd5b5061018c610265366004610c1f565b6105bf565b34801561027657600080fd5b5061018c610603565b34801561028b57600080fd5b506101d8600081565b3480156102a057600080fd5b5061018c6102af366004610bfb565b610617565b3480156102c057600080fd5b506101d8600281565b3480156102d557600080fd5b506000546001600160a01b031661014f565b3480156102f357600080fd5b5061018c610302366004610c38565b6106b2565b6101b5610315366004610c86565b61077f565b34801561032657600080fd5b506101d8600181565b34801561033b57600080fd5b5061018c61034a366004610bfb565b61084b565b34801561035b57600080fd5b506101b560045481565b34801561037157600080fd5b5061018c610380366004610bfb565b6108e7565b34801561039157600080fd5b5060035461014f906001600160a01b031681565b3480156103b157600080fd5b5061018c6103c0366004610bfb565b610985565b6001546001600160a01b031633146103f057604051631f0853c160e21b815260040160405180910390fd5b60ff84161561041c5760405163a0940ea960e01b815260ff851660048201526024015b60405180910390fd5b600354604051630111a9b760e11b81526001600160a01b0390911690630223536e90610452908b908b908b908b90600401610cf7565b600060405180830381600087803b15801561046c57600080fd5b505af1158015610480573d6000803e3d6000fd5b50506040805160ff8816815261ffff87166020820152908101859052606081018490528892507f58ec8d75f99798b0f5d8fe567e102c2006f27041b28ccf9c65e27c8ba010f807915060800160405180910390a25050505050505050565b6104e66109c3565b6000816001600160a01b03164760405160006040518083038185875af1925050503d8060008114610533576040519150601f19603f3d011682016040523d82523d6000602084013e610538565b606091505b505090508061057b5760405162461bcd60e51b815260206004820152600f60248201526e1dda5d1a191c985dc819985a5b1959608a1b6044820152606401610413565b5050565b6105876109c3565b60405133815281907feef04c31cb4b8b2e7ade37c9f258844c21380c82fad1b9af9529dc3b1326daba9060200160405180910390a250565b6105c76109c3565b60048190556040518181527f20461e09b8e557b77e107939f9ce6544698123aad0fc964ac5cc59b7df2e608f906020015b60405180910390a150565b61060b6109c3565b61061560006109f0565b565b61061f6109c3565b6001600160a01b0381166106645760405162461bcd60e51b815260206004820152600c60248201526b3d32b9379039b2b7322ab63760a11b6044820152606401610413565b600280546001600160a01b0319166001600160a01b0383169081179091556040519081527f81fee7bca94bea3e3e3560b9e8019e639c980511659ce436bd6e62790dbddbbd906020016105f8565b6001546001600160a01b031633146106dd57604051631f0853c160e21b815260040160405180910390fd5b600360ff85161115610707576040516360df9f8760e01b815260ff85166004820152602401610413565b60ff8416610728576040516330e4fe0360e11b815260040160405180910390fd5b6040805160ff8616815261ffff851660208201529081018390526060810182905285907f58ec8d75f99798b0f5d8fe567e102c2006f27041b28ccf9c65e27c8ba010f8079060800160405180910390a25050505050565b6002546000906001600160a01b031633146107ad57604051637f2e104960e01b815260040160405180910390fd5b7ff266d2fc560c9aaf31e6bdf020cac0f2a9d5b3aa16f932e16434749ddff149996107db6020860186610d41565b60408601356107f06080880160608901610d5c565b61080060a0890160808a01610bfb565b6040805163ffffffff959095168552602085019390935267ffffffffffffffff91909116838301526001600160a01b03166060830152519081900360800190a1506004549392505050565b6108536109c3565b6001600160a01b0381166108995760405162461bcd60e51b815260206004820152600d60248201526c3d32b9379037b832b930ba37b960991b6044820152606401610413565b600180546001600160a01b0319166001600160a01b0383169081179091556040519081527f99d737e0adf2c449d71890b86772885ec7959b152ddb265f76325b6e68e105d3906020016105f8565b6108ef6109c3565b6001600160a01b0381166109375760405162461bcd60e51b815260206004820152600f60248201526e3d32b937903932b1b2b4bb32aab63760891b6044820152606401610413565b600380546001600160a01b0319166001600160a01b0383169081179091556040519081527f549e7b08606f710537aa4c9f00cb4d403d0048a8049411f3c1922d6e97744d6a906020016105f8565b61098d6109c3565b6001600160a01b0381166109b757604051631e4fbdf760e01b815260006004820152602401610413565b6109c0816109f0565b50565b6000546001600160a01b031633146106155760405163118cdaa760e01b8152336004820152602401610413565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60008083601f840112610a5257600080fd5b50813567ffffffffffffffff811115610a6a57600080fd5b602083019150836020828501011115610a8257600080fd5b9250929050565b803567ffffffffffffffff81168114610aa157600080fd5b919050565b803560ff81168114610aa157600080fd5b803561ffff81168114610aa157600080fd5b60008060008060008060008060e0898b031215610ae557600080fd5b883567ffffffffffffffff811115610afc57600080fd5b610b088b828c01610a40565b90995097505060208901359550610b2160408a01610a89565b9450610b2f60608a01610aa6565b9350610b3d60808a01610ab7565b925060a0890135915060c089013590509295985092959890939650565b803563ffffffff81168114610aa157600080fd5b6001600160a01b03811681146109c057600080fd5b600080600080600060808688031215610b9b57600080fd5b610ba486610b5a565b9450610bb260208701610a89565b93506040860135610bc281610b6e565b9250606086013567ffffffffffffffff811115610bde57600080fd5b610bea88828901610a40565b969995985093965092949392505050565b600060208284031215610c0d57600080fd5b8135610c1881610b6e565b9392505050565b600060208284031215610c3157600080fd5b5035919050565b600080600080600060a08688031215610c5057600080fd5b85359450610c6060208701610aa6565b9350610c6e60408701610ab7565b94979396509394606081013594506080013592915050565b600080600060408486031215610c9b57600080fd5b833567ffffffffffffffff80821115610cb357600080fd5b9085019060a08288031215610cc757600080fd5b90935060208501359080821115610cdd57600080fd5b50610cea86828701610a40565b9497909650939450505050565b606081528360608201528385608083013760006080858301015260006080601f19601f870116830101905083602083015267ffffffffffffffff8316604083015295945050505050565b600060208284031215610d5357600080fd5b610c1882610b5a565b600060208284031215610d6e57600080fd5b610c1882610a8956fea2646970667358221220d94d4a34a82171ea20ebdc7b4651dff6411ce021ee5097ce9daee18d8b3512c364736f6c63430008160033", "devdoc": { "errors": { + "AllowNotSeparatelyRecorded()": [ + { + "details": "An allow rides along on `submitVerification`, so recording one separately would double-report the same outcome." + } + ], "OwnableInvalidOwner(address)": [ { "details": "The owner is not a valid owner account. (eg. `address(0)`)" @@ -429,26 +685,74 @@ { "details": "The caller account is not authorized to perform an operation." } + ], + "VerificationRequiresAllow(uint8)": [ + { + "details": "Submitting a verification asserts the packet was allowed; any other action would be a self-contradicting record." + } ] }, + "events": { + "RiskVerdict(bytes32,uint8,uint16,uint256,bytes32)": { + "params": { + "action": "ACTION_* below", + "evidenceHash": "keccak256 of the canonical evidence document held off-chain", + "payloadHash": "the packet this verdict is about", + "reasonMask": "bitmask of reason codes; bit assignments are append-only and documented in the worker's `assess/verdict.ts`", + "score": "0-100 risk score the action was derived from" + } + } + }, "kind": "dev", "methods": { + "approvePacket(bytes32)": { + "details": "Emits only; no storage. The worker observes `PacketApproved` and releases the packet from its local deferred queue. Approval is a human override of a risk verdict, so it is separated from the operator key by design — a compromised or buggy worker cannot approve the packets it chose to hold." + }, "owner()": { "details": "Returns the address of the current owner." }, + "recordVerdict(bytes32,uint8,uint16,uint256,bytes32)": { + "details": "Withholding the attestation is what actually stops the packet; this only leaves the audit trail. It is therefore best-effort by design — the worker treats a failure here as a lost record, never as a failure to enforce." + }, "renounceOwnership()": { "details": "Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner." }, + "submitVerification(bytes,bytes32,uint64,uint8,uint16,uint256,bytes32)": { + "details": "The verdict rides along at no extra transaction cost, so an allowed packet always carries an auditable reason for having been allowed. `action` must be ACTION_ALLOW: a packet that was blocked or held cannot also have been verified. An owner-approved release is reported as ACTION_ALLOW too — a human allowed it — with the reason mask still carrying why it had been held." + }, "transferOwnership(address)": { "details": "Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner." } }, + "stateVariables": { + "ACTION_ALLOW": { + "details": "Action codes. These are part of the event ABI: an indexer decoding old logs relies on them, so the numbering is permanent. Kept in sync with the worker's ACTION_CODES." + } + }, "title": "ComplianceDVN", "version": 1 }, "userdoc": { + "events": { + "PacketApproved(bytes32,address)": { + "notice": "A held packet cleared for verification by the owner. Deliberately owner-only: the worker holds only the operator key, so it cannot approve its own holds." + }, + "RiskVerdict(bytes32,uint8,uint16,uint256,bytes32)": { + "notice": "The risk decision behind a packet's outcome." + } + }, "kind": "user", - "methods": {}, + "methods": { + "approvePacket(bytes32)": { + "notice": "Clear a packet the worker withheld for manual review." + }, + "recordVerdict(bytes32,uint8,uint16,uint256,bytes32)": { + "notice": "Record a verdict for a packet that was NOT verified." + }, + "submitVerification(bytes,bytes32,uint64,uint8,uint16,uint256,bytes32)": { + "notice": "Attest a packet and record the risk verdict that permitted it, in one call." + } + }, "notice": "Thin LayerZero V2 DVN. All compliance judgment is off-chain; the on-chain contract only conforms to the worker-job interface and gates the destination attestation behind an operator key. Withholding `submitVerification` IS the veto.", "version": 1 }, @@ -473,7 +777,7 @@ { "astId": 253, "contract": "contracts/ComplianceDVN.sol:ComplianceDVN", - "label": "receiveUln", + "label": "sendUln", "offset": 0, "slot": "2", "type": "t_address" @@ -481,9 +785,17 @@ { "astId": 255, "contract": "contracts/ComplianceDVN.sol:ComplianceDVN", - "label": "fee", + "label": "receiveUln", "offset": 0, "slot": "3", + "type": "t_address" + }, + { + "astId": 257, + "contract": "contracts/ComplianceDVN.sol:ComplianceDVN", + "label": "fee", + "offset": 0, + "slot": "4", "type": "t_uint256" } ], diff --git a/deployments/base-sepolia/FakeStablecoinMock.json b/deployments/base-sepolia/FakeStablecoinMock.json new file mode 100644 index 0000000..97efc07 --- /dev/null +++ b/deployments/base-sepolia/FakeStablecoinMock.json @@ -0,0 +1,559 @@ +{ + "address": "0x1B48E40F971298b03B6AD0Ae3CA047CD11b7eA6e", + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "allowance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientAllowance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "ERC20InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC20InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "ERC20InvalidSender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "ERC20InvalidSpender", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "transactionHash": "0xecff88fe86b381a726f752956adcce79dd3b63f0c708551fe92ef6115d26b829", + "receipt": { + "to": null, + "from": "0x8583894d0e57e42abb83039537f314490038efa0", + "contractAddress": "0x1B48E40F971298b03B6AD0Ae3CA047CD11b7eA6e", + "transactionIndex": 7, + "gasUsed": "526781", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xecff88fe86b381a726f752956adcce79dd3b63f0c708551fe92ef6115d26b829", + "logs": [], + "blockNumber": 44881966, + "cumulativeGasUsed": "1993129", + "status": 1, + "byzantium": true + }, + "args": [], + "numDeployments": 1, + "solcInputHash": "31caad50e704c80e4a3252d0a262b59d", + "metadata": "{\"compiler\":{\"version\":\"0.8.22+commit.4fc1097e\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"allowance\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"needed\",\"type\":\"uint256\"}],\"name\":\"ERC20InsufficientAllowance\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"needed\",\"type\":\"uint256\"}],\"name\":\"ERC20InsufficientBalance\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"approver\",\"type\":\"address\"}],\"name\":\"ERC20InvalidApprover\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"}],\"name\":\"ERC20InvalidReceiver\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"ERC20InvalidSender\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"ERC20InvalidSpender\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"mint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"details\":\"The engine's token screening resolves a subject's underlying token through `token()`, reads `symbol()`/`decimals()`, and compares the address against the chain's canonical issuer (see `CANONICAL_STABLECOINS` in worker/assess/providers/token.ts). Claiming a watched symbol from a non-canonical address is exactly the pattern `fake_stablecoin_suspect` exists to catch \\u2014 so this contract asserts the symbol and nothing else. Deploy only to testnets.\",\"errors\":{\"ERC20InsufficientAllowance(address,uint256,uint256)\":[{\"details\":\"Indicates a failure with the `spender`\\u2019s `allowance`. Used in transfers.\",\"params\":{\"allowance\":\"Amount of tokens a `spender` is allowed to operate with.\",\"needed\":\"Minimum amount required to perform a transfer.\",\"spender\":\"Address that may be allowed to operate on tokens without being their owner.\"}}],\"ERC20InsufficientBalance(address,uint256,uint256)\":[{\"details\":\"Indicates an error related to the current `balance` of a `sender`. Used in transfers.\",\"params\":{\"balance\":\"Current balance for the interacting account.\",\"needed\":\"Minimum amount required to perform a transfer.\",\"sender\":\"Address whose tokens are being transferred.\"}}],\"ERC20InvalidApprover(address)\":[{\"details\":\"Indicates a failure with the `approver` of a token to be approved. Used in approvals.\",\"params\":{\"approver\":\"Address initiating an approval operation.\"}}],\"ERC20InvalidReceiver(address)\":[{\"details\":\"Indicates a failure with the token `receiver`. Used in transfers.\",\"params\":{\"receiver\":\"Address to which tokens are being transferred.\"}}],\"ERC20InvalidSender(address)\":[{\"details\":\"Indicates a failure with the token `sender`. Used in transfers.\",\"params\":{\"sender\":\"Address whose tokens are being transferred.\"}}],\"ERC20InvalidSpender(address)\":[{\"details\":\"Indicates a failure with the `spender` to be approved. Used in approvals.\",\"params\":{\"spender\":\"Address that may be allowed to operate on tokens without being their owner.\"}}]},\"events\":{\"Approval(address,address,uint256)\":{\"details\":\"Emitted when the allowance of a `spender` for an `owner` is set by a call to {approve}. `value` is the new allowance.\"},\"Transfer(address,address,uint256)\":{\"details\":\"Emitted when `value` tokens are moved from one account (`from`) to another (`to`). Note that `value` may be zero.\"}},\"kind\":\"dev\",\"methods\":{\"allowance(address,address)\":{\"details\":\"Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` through {transferFrom}. This is zero by default. This value changes when {approve} or {transferFrom} are called.\"},\"approve(address,uint256)\":{\"details\":\"See {IERC20-approve}. NOTE: If `value` is the maximum `uint256`, the allowance is not updated on `transferFrom`. This is semantically equivalent to an infinite approval. Requirements: - `spender` cannot be the zero address.\"},\"balanceOf(address)\":{\"details\":\"Returns the value of tokens owned by `account`.\"},\"decimals()\":{\"details\":\"Six, like the real thing: the check is about the address, and matching the decimals keeps the decoy from being dismissed on a detail the engine does not rely on.\"},\"name()\":{\"details\":\"Returns the name of the token.\"},\"symbol()\":{\"details\":\"Returns the symbol of the token, usually a shorter version of the name.\"},\"token()\":{\"details\":\"This is what makes the engine treat the address as a token rather than a plain OApp: `resolveToken` calls `token()` and screens whatever address comes back.\"},\"totalSupply()\":{\"details\":\"Returns the value of tokens in existence.\"},\"transfer(address,uint256)\":{\"details\":\"See {IERC20-transfer}. Requirements: - `to` cannot be the zero address. - the caller must have a balance of at least `value`.\"},\"transferFrom(address,address,uint256)\":{\"details\":\"See {IERC20-transferFrom}. Skips emitting an {Approval} event indicating an allowance update. This is not required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve]. NOTE: Does not update the allowance if the current allowance is the maximum `uint256`. Requirements: - `from` and `to` cannot be the zero address. - `from` must have a balance of at least `value`. - the caller must have allowance for ``from``'s tokens of at least `value`.\"}},\"title\":\"FakeStablecoinMock\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"mint(address,uint256)\":{\"notice\":\"Open mint, testnet only \\u2014 a decoy with no supply is harder to look at in an explorer.\"},\"token()\":{\"notice\":\"Reports itself as its own underlying token.\"}},\"notice\":\"A testnet decoy that claims to be USDC, for exercising the risk engine's impersonation check. It is NOT a stablecoin and holds no value.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/mocks/FakeStablecoinMock.sol\":\"FakeStablecoinMock\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/interfaces/draft-IERC6093.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.5.0) (interfaces/draft-IERC6093.sol)\\n\\npragma solidity >=0.8.4;\\n\\n/**\\n * @dev Standard ERC-20 Errors\\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-20 tokens.\\n */\\ninterface IERC20Errors {\\n /**\\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n * @param balance Current balance for the interacting account.\\n * @param needed Minimum amount required to perform a transfer.\\n */\\n error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);\\n\\n /**\\n * @dev Indicates a failure with the token `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n */\\n error ERC20InvalidSender(address sender);\\n\\n /**\\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\\n * @param receiver Address to which tokens are being transferred.\\n */\\n error ERC20InvalidReceiver(address receiver);\\n\\n /**\\n * @dev Indicates a failure with the `spender`\\u2019s `allowance`. Used in transfers.\\n * @param spender Address that may be allowed to operate on tokens without being their owner.\\n * @param allowance Amount of tokens a `spender` is allowed to operate with.\\n * @param needed Minimum amount required to perform a transfer.\\n */\\n error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);\\n\\n /**\\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\\n * @param approver Address initiating an approval operation.\\n */\\n error ERC20InvalidApprover(address approver);\\n\\n /**\\n * @dev Indicates a failure with the `spender` to be approved. Used in approvals.\\n * @param spender Address that may be allowed to operate on tokens without being their owner.\\n */\\n error ERC20InvalidSpender(address spender);\\n}\\n\\n/**\\n * @dev Standard ERC-721 Errors\\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-721 tokens.\\n */\\ninterface IERC721Errors {\\n /**\\n * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-721.\\n * Used in balance queries.\\n * @param owner Address of the current owner of a token.\\n */\\n error ERC721InvalidOwner(address owner);\\n\\n /**\\n * @dev Indicates a `tokenId` whose `owner` is the zero address.\\n * @param tokenId Identifier number of a token.\\n */\\n error ERC721NonexistentToken(uint256 tokenId);\\n\\n /**\\n * @dev Indicates an error related to the ownership over a particular token. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n * @param tokenId Identifier number of a token.\\n * @param owner Address of the current owner of a token.\\n */\\n error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);\\n\\n /**\\n * @dev Indicates a failure with the token `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n */\\n error ERC721InvalidSender(address sender);\\n\\n /**\\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\\n * @param receiver Address to which tokens are being transferred.\\n */\\n error ERC721InvalidReceiver(address receiver);\\n\\n /**\\n * @dev Indicates a failure with the `operator`\\u2019s approval. Used in transfers.\\n * @param operator Address that may be allowed to operate on tokens without being their owner.\\n * @param tokenId Identifier number of a token.\\n */\\n error ERC721InsufficientApproval(address operator, uint256 tokenId);\\n\\n /**\\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\\n * @param approver Address initiating an approval operation.\\n */\\n error ERC721InvalidApprover(address approver);\\n\\n /**\\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\\n * @param operator Address that may be allowed to operate on tokens without being their owner.\\n */\\n error ERC721InvalidOperator(address operator);\\n}\\n\\n/**\\n * @dev Standard ERC-1155 Errors\\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-1155 tokens.\\n */\\ninterface IERC1155Errors {\\n /**\\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n * @param balance Current balance for the interacting account.\\n * @param needed Minimum amount required to perform a transfer.\\n * @param tokenId Identifier number of a token.\\n */\\n error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);\\n\\n /**\\n * @dev Indicates a failure with the token `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n */\\n error ERC1155InvalidSender(address sender);\\n\\n /**\\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\\n * @param receiver Address to which tokens are being transferred.\\n */\\n error ERC1155InvalidReceiver(address receiver);\\n\\n /**\\n * @dev Indicates a failure with the `operator`\\u2019s approval. Used in transfers.\\n * @param operator Address that may be allowed to operate on tokens without being their owner.\\n * @param owner Address of the current owner of a token.\\n */\\n error ERC1155MissingApprovalForAll(address operator, address owner);\\n\\n /**\\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\\n * @param approver Address initiating an approval operation.\\n */\\n error ERC1155InvalidApprover(address approver);\\n\\n /**\\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\\n * @param operator Address that may be allowed to operate on tokens without being their owner.\\n */\\n error ERC1155InvalidOperator(address operator);\\n\\n /**\\n * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.\\n * Used in batch transfers.\\n * @param idsLength Length of the array of token identifiers\\n * @param valuesLength Length of the array of token amounts\\n */\\n error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);\\n}\\n\",\"keccak256\":\"0x1b88b3fb3d85ba5496d7d5f396f83ee1fddcdd6762059ff65992655b67920998\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/ERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.5.0) (token/ERC20/ERC20.sol)\\n\\npragma solidity ^0.8.20;\\n\\nimport {IERC20} from \\\"./IERC20.sol\\\";\\nimport {IERC20Metadata} from \\\"./extensions/IERC20Metadata.sol\\\";\\nimport {Context} from \\\"../../utils/Context.sol\\\";\\nimport {IERC20Errors} from \\\"../../interfaces/draft-IERC6093.sol\\\";\\n\\n/**\\n * @dev Implementation of the {IERC20} interface.\\n *\\n * This implementation is agnostic to the way tokens are created. This means\\n * that a supply mechanism has to be added in a derived contract using {_mint}.\\n *\\n * TIP: For a detailed writeup see our guide\\n * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How\\n * to implement supply mechanisms].\\n *\\n * The default value of {decimals} is 18. To change this, you should override\\n * this function so it returns a different value.\\n *\\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\\n * instead returning `false` on failure. This behavior is nonetheless\\n * conventional and does not conflict with the expectations of ERC-20\\n * applications.\\n */\\nabstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {\\n mapping(address account => uint256) private _balances;\\n\\n mapping(address account => mapping(address spender => uint256)) private _allowances;\\n\\n uint256 private _totalSupply;\\n\\n string private _name;\\n string private _symbol;\\n\\n /**\\n * @dev Sets the values for {name} and {symbol}.\\n *\\n * Both values are immutable: they can only be set once during construction.\\n */\\n constructor(string memory name_, string memory symbol_) {\\n _name = name_;\\n _symbol = symbol_;\\n }\\n\\n /**\\n * @dev Returns the name of the token.\\n */\\n function name() public view virtual returns (string memory) {\\n return _name;\\n }\\n\\n /**\\n * @dev Returns the symbol of the token, usually a shorter version of the\\n * name.\\n */\\n function symbol() public view virtual returns (string memory) {\\n return _symbol;\\n }\\n\\n /**\\n * @dev Returns the number of decimals used to get its user representation.\\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\\n *\\n * Tokens usually opt for a value of 18, imitating the relationship between\\n * Ether and Wei. This is the default value returned by this function, unless\\n * it's overridden.\\n *\\n * NOTE: This information is only used for _display_ purposes: it in\\n * no way affects any of the arithmetic of the contract, including\\n * {IERC20-balanceOf} and {IERC20-transfer}.\\n */\\n function decimals() public view virtual returns (uint8) {\\n return 18;\\n }\\n\\n /// @inheritdoc IERC20\\n function totalSupply() public view virtual returns (uint256) {\\n return _totalSupply;\\n }\\n\\n /// @inheritdoc IERC20\\n function balanceOf(address account) public view virtual returns (uint256) {\\n return _balances[account];\\n }\\n\\n /**\\n * @dev See {IERC20-transfer}.\\n *\\n * Requirements:\\n *\\n * - `to` cannot be the zero address.\\n * - the caller must have a balance of at least `value`.\\n */\\n function transfer(address to, uint256 value) public virtual returns (bool) {\\n address owner = _msgSender();\\n _transfer(owner, to, value);\\n return true;\\n }\\n\\n /// @inheritdoc IERC20\\n function allowance(address owner, address spender) public view virtual returns (uint256) {\\n return _allowances[owner][spender];\\n }\\n\\n /**\\n * @dev See {IERC20-approve}.\\n *\\n * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on\\n * `transferFrom`. This is semantically equivalent to an infinite approval.\\n *\\n * Requirements:\\n *\\n * - `spender` cannot be the zero address.\\n */\\n function approve(address spender, uint256 value) public virtual returns (bool) {\\n address owner = _msgSender();\\n _approve(owner, spender, value);\\n return true;\\n }\\n\\n /**\\n * @dev See {IERC20-transferFrom}.\\n *\\n * Skips emitting an {Approval} event indicating an allowance update. This is not\\n * required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].\\n *\\n * NOTE: Does not update the allowance if the current allowance\\n * is the maximum `uint256`.\\n *\\n * Requirements:\\n *\\n * - `from` and `to` cannot be the zero address.\\n * - `from` must have a balance of at least `value`.\\n * - the caller must have allowance for ``from``'s tokens of at least\\n * `value`.\\n */\\n function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {\\n address spender = _msgSender();\\n _spendAllowance(from, spender, value);\\n _transfer(from, to, value);\\n return true;\\n }\\n\\n /**\\n * @dev Moves a `value` amount of tokens from `from` to `to`.\\n *\\n * This internal function is equivalent to {transfer}, and can be used to\\n * e.g. implement automatic token fees, slashing mechanisms, etc.\\n *\\n * Emits a {Transfer} event.\\n *\\n * NOTE: This function is not virtual, {_update} should be overridden instead.\\n */\\n function _transfer(address from, address to, uint256 value) internal {\\n if (from == address(0)) {\\n revert ERC20InvalidSender(address(0));\\n }\\n if (to == address(0)) {\\n revert ERC20InvalidReceiver(address(0));\\n }\\n _update(from, to, value);\\n }\\n\\n /**\\n * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`\\n * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding\\n * this function.\\n *\\n * Emits a {Transfer} event.\\n */\\n function _update(address from, address to, uint256 value) internal virtual {\\n if (from == address(0)) {\\n // Overflow check required: The rest of the code assumes that totalSupply never overflows\\n _totalSupply += value;\\n } else {\\n uint256 fromBalance = _balances[from];\\n if (fromBalance < value) {\\n revert ERC20InsufficientBalance(from, fromBalance, value);\\n }\\n unchecked {\\n // Overflow not possible: value <= fromBalance <= totalSupply.\\n _balances[from] = fromBalance - value;\\n }\\n }\\n\\n if (to == address(0)) {\\n unchecked {\\n // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.\\n _totalSupply -= value;\\n }\\n } else {\\n unchecked {\\n // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.\\n _balances[to] += value;\\n }\\n }\\n\\n emit Transfer(from, to, value);\\n }\\n\\n /**\\n * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).\\n * Relies on the `_update` mechanism\\n *\\n * Emits a {Transfer} event with `from` set to the zero address.\\n *\\n * NOTE: This function is not virtual, {_update} should be overridden instead.\\n */\\n function _mint(address account, uint256 value) internal {\\n if (account == address(0)) {\\n revert ERC20InvalidReceiver(address(0));\\n }\\n _update(address(0), account, value);\\n }\\n\\n /**\\n * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.\\n * Relies on the `_update` mechanism.\\n *\\n * Emits a {Transfer} event with `to` set to the zero address.\\n *\\n * NOTE: This function is not virtual, {_update} should be overridden instead\\n */\\n function _burn(address account, uint256 value) internal {\\n if (account == address(0)) {\\n revert ERC20InvalidSender(address(0));\\n }\\n _update(account, address(0), value);\\n }\\n\\n /**\\n * @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.\\n *\\n * This internal function is equivalent to `approve`, and can be used to\\n * e.g. set automatic allowances for certain subsystems, etc.\\n *\\n * Emits an {Approval} event.\\n *\\n * Requirements:\\n *\\n * - `owner` cannot be the zero address.\\n * - `spender` cannot be the zero address.\\n *\\n * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.\\n */\\n function _approve(address owner, address spender, uint256 value) internal {\\n _approve(owner, spender, value, true);\\n }\\n\\n /**\\n * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.\\n *\\n * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by\\n * `_spendAllowance` during the `transferFrom` operation sets the flag to false. This saves gas by not emitting any\\n * `Approval` event during `transferFrom` operations.\\n *\\n * Anyone who wishes to continue emitting `Approval` events on the `transferFrom` operation can force the flag to\\n * true using the following override:\\n *\\n * ```solidity\\n * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {\\n * super._approve(owner, spender, value, true);\\n * }\\n * ```\\n *\\n * Requirements are the same as {_approve}.\\n */\\n function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {\\n if (owner == address(0)) {\\n revert ERC20InvalidApprover(address(0));\\n }\\n if (spender == address(0)) {\\n revert ERC20InvalidSpender(address(0));\\n }\\n _allowances[owner][spender] = value;\\n if (emitEvent) {\\n emit Approval(owner, spender, value);\\n }\\n }\\n\\n /**\\n * @dev Updates `owner`'s allowance for `spender` based on spent `value`.\\n *\\n * Does not update the allowance value in case of infinite allowance.\\n * Revert if not enough allowance is available.\\n *\\n * Does not emit an {Approval} event.\\n */\\n function _spendAllowance(address owner, address spender, uint256 value) internal virtual {\\n uint256 currentAllowance = allowance(owner, spender);\\n if (currentAllowance < type(uint256).max) {\\n if (currentAllowance < value) {\\n revert ERC20InsufficientAllowance(spender, currentAllowance, value);\\n }\\n unchecked {\\n _approve(owner, spender, currentAllowance - value, false);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x669464167428061ee0f8618b73b3ee90aff8405683e7ddde8cd77dadaa1afe29\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)\\n\\npragma solidity >=0.4.16;\\n\\n/**\\n * @dev Interface of the ERC-20 standard as defined in the ERC.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n\\n /**\\n * @dev Returns the value of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the value of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from the caller's account to `to`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address to, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\\n * caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from `from` to `to` using the\\n * allowance mechanism. `value` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(address from, address to, uint256 value) external returns (bool);\\n}\\n\",\"keccak256\":\"0x74ed01eb66b923d0d0cfe3be84604ac04b76482a55f9dd655e1ef4d367f95bc2\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/extensions/IERC20Metadata.sol)\\n\\npragma solidity >=0.6.2;\\n\\nimport {IERC20} from \\\"../IERC20.sol\\\";\\n\\n/**\\n * @dev Interface for the optional metadata functions from the ERC-20 standard.\\n */\\ninterface IERC20Metadata is IERC20 {\\n /**\\n * @dev Returns the name of the token.\\n */\\n function name() external view returns (string memory);\\n\\n /**\\n * @dev Returns the symbol of the token.\\n */\\n function symbol() external view returns (string memory);\\n\\n /**\\n * @dev Returns the decimals places of the token.\\n */\\n function decimals() external view returns (uint8);\\n}\\n\",\"keccak256\":\"0xd6fa4088198f04eef10c5bce8a2f4d60554b7ec4b987f684393c01bf79b94d9f\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Context.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\\n\\npragma solidity ^0.8.20;\\n\\n/**\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract Context {\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n\\n function _contextSuffixLength() internal view virtual returns (uint256) {\\n return 0;\\n }\\n}\\n\",\"keccak256\":\"0x493033a8d1b176a037b2cc6a04dad01a5c157722049bbecf632ca876224dd4b2\",\"license\":\"MIT\"},\"contracts/mocks/FakeStablecoinMock.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.22;\\n\\nimport { ERC20 } from \\\"@openzeppelin/contracts/token/ERC20/ERC20.sol\\\";\\n\\n/// @title FakeStablecoinMock\\n/// @notice A testnet decoy that claims to be USDC, for exercising the risk engine's\\n/// impersonation check. It is NOT a stablecoin and holds no value.\\n/// @dev The engine's token screening resolves a subject's underlying token through `token()`,\\n/// reads `symbol()`/`decimals()`, and compares the address against the chain's canonical\\n/// issuer (see `CANONICAL_STABLECOINS` in worker/assess/providers/token.ts). Claiming a\\n/// watched symbol from a non-canonical address is exactly the pattern\\n/// `fake_stablecoin_suspect` exists to catch \\u2014 so this contract asserts the symbol and\\n/// nothing else. Deploy only to testnets.\\ncontract FakeStablecoinMock is ERC20 {\\n constructor() ERC20(\\\"USD Coin\\\", \\\"USDC\\\") {}\\n\\n /// @dev Six, like the real thing: the check is about the address, and matching the decimals\\n /// keeps the decoy from being dismissed on a detail the engine does not rely on.\\n function decimals() public pure override returns (uint8) {\\n return 6;\\n }\\n\\n /// @notice Reports itself as its own underlying token.\\n /// @dev This is what makes the engine treat the address as a token rather than a plain OApp:\\n /// `resolveToken` calls `token()` and screens whatever address comes back.\\n function token() external view returns (address) {\\n return address(this);\\n }\\n\\n /// @notice Open mint, testnet only \\u2014 a decoy with no supply is harder to look at in an explorer.\\n function mint(address _to, uint256 _amount) external {\\n _mint(_to, _amount);\\n }\\n}\\n\",\"keccak256\":\"0xb593eb06f63d8e10e8913c6b398d5e26f3185d91ae8f7042147bb8cf6f963287\",\"license\":\"MIT\"}},\"version\":1}", + "bytecode": "0x608060405234801561001057600080fd5b50604051806040016040528060088152602001672aa9a21021b7b4b760c11b815250604051806040016040528060048152602001635553444360e01b815250816003908161005e9190610114565b50600461006b8282610114565b5050506101d3565b634e487b7160e01b600052604160045260246000fd5b600181811c9082168061009d57607f821691505b6020821081036100bd57634e487b7160e01b600052602260045260246000fd5b50919050565b601f82111561010f576000816000526020600020601f850160051c810160208610156100ec5750805b601f850160051c820191505b8181101561010b578281556001016100f8565b5050505b505050565b81516001600160401b0381111561012d5761012d610073565b6101418161013b8454610089565b846100c3565b602080601f831160018114610176576000841561015e5750858301515b600019600386901b1c1916600185901b17855561010b565b600085815260208120601f198616915b828110156101a557888601518255948401946001909101908401610186565b50858210156101c35787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b61079f806101e26000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c806340c10f191161007157806340c10f191461012357806370a082311461013857806395d89b4114610161578063a9059cbb14610169578063dd62ed3e1461017c578063fc0c546a146101b557600080fd5b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100ef57806323b872dd14610101578063313ce56714610114575b600080fd5b6100b66101c3565b6040516100c391906105e8565b60405180910390f35b6100df6100da366004610653565b610255565b60405190151581526020016100c3565b6002545b6040519081526020016100c3565b6100df61010f36600461067d565b61026f565b604051600681526020016100c3565b610136610131366004610653565b610293565b005b6100f36101463660046106b9565b6001600160a01b031660009081526020819052604090205490565b6100b66102a1565b6100df610177366004610653565b6102b0565b6100f361018a3660046106db565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6040513081526020016100c3565b6060600380546101d29061070e565b80601f01602080910402602001604051908101604052809291908181526020018280546101fe9061070e565b801561024b5780601f106102205761010080835404028352916020019161024b565b820191906000526020600020905b81548152906001019060200180831161022e57829003601f168201915b5050505050905090565b6000336102638185856102be565b60019150505b92915050565b60003361027d8582856102d0565b610288858585610354565b506001949350505050565b61029d82826103b3565b5050565b6060600480546101d29061070e565b600033610263818585610354565b6102cb83838360016103e9565b505050565b6001600160a01b0383811660009081526001602090815260408083209386168352929052205460001981101561034e578181101561033f57604051637dc7a0d960e11b81526001600160a01b038416600482015260248101829052604481018390526064015b60405180910390fd5b61034e848484840360006103e9565b50505050565b6001600160a01b03831661037e57604051634b637e8f60e11b815260006004820152602401610336565b6001600160a01b0382166103a85760405163ec442f0560e01b815260006004820152602401610336565b6102cb8383836104be565b6001600160a01b0382166103dd5760405163ec442f0560e01b815260006004820152602401610336565b61029d600083836104be565b6001600160a01b0384166104135760405163e602df0560e01b815260006004820152602401610336565b6001600160a01b03831661043d57604051634a1406b160e11b815260006004820152602401610336565b6001600160a01b038085166000908152600160209081526040808320938716835292905220829055801561034e57826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040516104b091815260200190565b60405180910390a350505050565b6001600160a01b0383166104e95780600260008282546104de9190610748565b9091555061055b9050565b6001600160a01b0383166000908152602081905260409020548181101561053c5760405163391434e360e21b81526001600160a01b03851660048201526024810182905260448101839052606401610336565b6001600160a01b03841660009081526020819052604090209082900390555b6001600160a01b03821661057757600280548290039055610596565b6001600160a01b03821660009081526020819052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516105db91815260200190565b60405180910390a3505050565b60006020808352835180602085015260005b81811015610616578581018301518582016040015282016105fa565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b038116811461064e57600080fd5b919050565b6000806040838503121561066657600080fd5b61066f83610637565b946020939093013593505050565b60008060006060848603121561069257600080fd5b61069b84610637565b92506106a960208501610637565b9150604084013590509250925092565b6000602082840312156106cb57600080fd5b6106d482610637565b9392505050565b600080604083850312156106ee57600080fd5b6106f783610637565b915061070560208401610637565b90509250929050565b600181811c9082168061072257607f821691505b60208210810361074257634e487b7160e01b600052602260045260246000fd5b50919050565b8082018082111561026957634e487b7160e01b600052601160045260246000fdfea2646970667358221220c4840d9c77eca3ef0ead0a97a130e6b366b42fde6b4af36b1a94a13b78cc069664736f6c63430008160033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100a95760003560e01c806340c10f191161007157806340c10f191461012357806370a082311461013857806395d89b4114610161578063a9059cbb14610169578063dd62ed3e1461017c578063fc0c546a146101b557600080fd5b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100ef57806323b872dd14610101578063313ce56714610114575b600080fd5b6100b66101c3565b6040516100c391906105e8565b60405180910390f35b6100df6100da366004610653565b610255565b60405190151581526020016100c3565b6002545b6040519081526020016100c3565b6100df61010f36600461067d565b61026f565b604051600681526020016100c3565b610136610131366004610653565b610293565b005b6100f36101463660046106b9565b6001600160a01b031660009081526020819052604090205490565b6100b66102a1565b6100df610177366004610653565b6102b0565b6100f361018a3660046106db565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6040513081526020016100c3565b6060600380546101d29061070e565b80601f01602080910402602001604051908101604052809291908181526020018280546101fe9061070e565b801561024b5780601f106102205761010080835404028352916020019161024b565b820191906000526020600020905b81548152906001019060200180831161022e57829003601f168201915b5050505050905090565b6000336102638185856102be565b60019150505b92915050565b60003361027d8582856102d0565b610288858585610354565b506001949350505050565b61029d82826103b3565b5050565b6060600480546101d29061070e565b600033610263818585610354565b6102cb83838360016103e9565b505050565b6001600160a01b0383811660009081526001602090815260408083209386168352929052205460001981101561034e578181101561033f57604051637dc7a0d960e11b81526001600160a01b038416600482015260248101829052604481018390526064015b60405180910390fd5b61034e848484840360006103e9565b50505050565b6001600160a01b03831661037e57604051634b637e8f60e11b815260006004820152602401610336565b6001600160a01b0382166103a85760405163ec442f0560e01b815260006004820152602401610336565b6102cb8383836104be565b6001600160a01b0382166103dd5760405163ec442f0560e01b815260006004820152602401610336565b61029d600083836104be565b6001600160a01b0384166104135760405163e602df0560e01b815260006004820152602401610336565b6001600160a01b03831661043d57604051634a1406b160e11b815260006004820152602401610336565b6001600160a01b038085166000908152600160209081526040808320938716835292905220829055801561034e57826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040516104b091815260200190565b60405180910390a350505050565b6001600160a01b0383166104e95780600260008282546104de9190610748565b9091555061055b9050565b6001600160a01b0383166000908152602081905260409020548181101561053c5760405163391434e360e21b81526001600160a01b03851660048201526024810182905260448101839052606401610336565b6001600160a01b03841660009081526020819052604090209082900390555b6001600160a01b03821661057757600280548290039055610596565b6001600160a01b03821660009081526020819052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516105db91815260200190565b60405180910390a3505050565b60006020808352835180602085015260005b81811015610616578581018301518582016040015282016105fa565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b038116811461064e57600080fd5b919050565b6000806040838503121561066657600080fd5b61066f83610637565b946020939093013593505050565b60008060006060848603121561069257600080fd5b61069b84610637565b92506106a960208501610637565b9150604084013590509250925092565b6000602082840312156106cb57600080fd5b6106d482610637565b9392505050565b600080604083850312156106ee57600080fd5b6106f783610637565b915061070560208401610637565b90509250929050565b600181811c9082168061072257607f821691505b60208210810361074257634e487b7160e01b600052602260045260246000fd5b50919050565b8082018082111561026957634e487b7160e01b600052601160045260246000fdfea2646970667358221220c4840d9c77eca3ef0ead0a97a130e6b366b42fde6b4af36b1a94a13b78cc069664736f6c63430008160033", + "devdoc": { + "details": "The engine's token screening resolves a subject's underlying token through `token()`, reads `symbol()`/`decimals()`, and compares the address against the chain's canonical issuer (see `CANONICAL_STABLECOINS` in worker/assess/providers/token.ts). Claiming a watched symbol from a non-canonical address is exactly the pattern `fake_stablecoin_suspect` exists to catch — so this contract asserts the symbol and nothing else. Deploy only to testnets.", + "errors": { + "ERC20InsufficientAllowance(address,uint256,uint256)": [ + { + "details": "Indicates a failure with the `spender`’s `allowance`. Used in transfers.", + "params": { + "allowance": "Amount of tokens a `spender` is allowed to operate with.", + "needed": "Minimum amount required to perform a transfer.", + "spender": "Address that may be allowed to operate on tokens without being their owner." + } + } + ], + "ERC20InsufficientBalance(address,uint256,uint256)": [ + { + "details": "Indicates an error related to the current `balance` of a `sender`. Used in transfers.", + "params": { + "balance": "Current balance for the interacting account.", + "needed": "Minimum amount required to perform a transfer.", + "sender": "Address whose tokens are being transferred." + } + } + ], + "ERC20InvalidApprover(address)": [ + { + "details": "Indicates a failure with the `approver` of a token to be approved. Used in approvals.", + "params": { + "approver": "Address initiating an approval operation." + } + } + ], + "ERC20InvalidReceiver(address)": [ + { + "details": "Indicates a failure with the token `receiver`. Used in transfers.", + "params": { + "receiver": "Address to which tokens are being transferred." + } + } + ], + "ERC20InvalidSender(address)": [ + { + "details": "Indicates a failure with the token `sender`. Used in transfers.", + "params": { + "sender": "Address whose tokens are being transferred." + } + } + ], + "ERC20InvalidSpender(address)": [ + { + "details": "Indicates a failure with the `spender` to be approved. Used in approvals.", + "params": { + "spender": "Address that may be allowed to operate on tokens without being their owner." + } + } + ] + }, + "events": { + "Approval(address,address,uint256)": { + "details": "Emitted when the allowance of a `spender` for an `owner` is set by a call to {approve}. `value` is the new allowance." + }, + "Transfer(address,address,uint256)": { + "details": "Emitted when `value` tokens are moved from one account (`from`) to another (`to`). Note that `value` may be zero." + } + }, + "kind": "dev", + "methods": { + "allowance(address,address)": { + "details": "Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` through {transferFrom}. This is zero by default. This value changes when {approve} or {transferFrom} are called." + }, + "approve(address,uint256)": { + "details": "See {IERC20-approve}. NOTE: If `value` is the maximum `uint256`, the allowance is not updated on `transferFrom`. This is semantically equivalent to an infinite approval. Requirements: - `spender` cannot be the zero address." + }, + "balanceOf(address)": { + "details": "Returns the value of tokens owned by `account`." + }, + "decimals()": { + "details": "Six, like the real thing: the check is about the address, and matching the decimals keeps the decoy from being dismissed on a detail the engine does not rely on." + }, + "name()": { + "details": "Returns the name of the token." + }, + "symbol()": { + "details": "Returns the symbol of the token, usually a shorter version of the name." + }, + "token()": { + "details": "This is what makes the engine treat the address as a token rather than a plain OApp: `resolveToken` calls `token()` and screens whatever address comes back." + }, + "totalSupply()": { + "details": "Returns the value of tokens in existence." + }, + "transfer(address,uint256)": { + "details": "See {IERC20-transfer}. Requirements: - `to` cannot be the zero address. - the caller must have a balance of at least `value`." + }, + "transferFrom(address,address,uint256)": { + "details": "See {IERC20-transferFrom}. Skips emitting an {Approval} event indicating an allowance update. This is not required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve]. NOTE: Does not update the allowance if the current allowance is the maximum `uint256`. Requirements: - `from` and `to` cannot be the zero address. - `from` must have a balance of at least `value`. - the caller must have allowance for ``from``'s tokens of at least `value`." + } + }, + "title": "FakeStablecoinMock", + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "mint(address,uint256)": { + "notice": "Open mint, testnet only — a decoy with no supply is harder to look at in an explorer." + }, + "token()": { + "notice": "Reports itself as its own underlying token." + } + }, + "notice": "A testnet decoy that claims to be USDC, for exercising the risk engine's impersonation check. It is NOT a stablecoin and holds no value.", + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 159, + "contract": "contracts/mocks/FakeStablecoinMock.sol:FakeStablecoinMock", + "label": "_balances", + "offset": 0, + "slot": "0", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 165, + "contract": "contracts/mocks/FakeStablecoinMock.sol:FakeStablecoinMock", + "label": "_allowances", + "offset": 0, + "slot": "1", + "type": "t_mapping(t_address,t_mapping(t_address,t_uint256))" + }, + { + "astId": 167, + "contract": "contracts/mocks/FakeStablecoinMock.sol:FakeStablecoinMock", + "label": "_totalSupply", + "offset": 0, + "slot": "2", + "type": "t_uint256" + }, + { + "astId": 169, + "contract": "contracts/mocks/FakeStablecoinMock.sol:FakeStablecoinMock", + "label": "_name", + "offset": 0, + "slot": "3", + "type": "t_string_storage" + }, + { + "astId": 171, + "contract": "contracts/mocks/FakeStablecoinMock.sol:FakeStablecoinMock", + "label": "_symbol", + "offset": 0, + "slot": "4", + "type": "t_string_storage" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32", + "value": "t_mapping(t_address,t_uint256)" + }, + "t_mapping(t_address,t_uint256)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_string_storage": { + "encoding": "bytes", + "label": "string", + "numberOfBytes": "32" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + } + } + } +} \ No newline at end of file diff --git a/deployments/base-sepolia/FakeUsdcOFT.json b/deployments/base-sepolia/FakeUsdcOFT.json new file mode 100644 index 0000000..4dde1fe --- /dev/null +++ b/deployments/base-sepolia/FakeUsdcOFT.json @@ -0,0 +1,2191 @@ +{ + "address": "0xcE65144C75d77c479b7FF12Cff41a1AC5359A578", + "abi": [ + { + "inputs": [ + { + "internalType": "string", + "name": "_name", + "type": "string" + }, + { + "internalType": "string", + "name": "_symbol", + "type": "string" + }, + { + "internalType": "address", + "name": "_lzEndpoint", + "type": "address" + }, + { + "internalType": "address", + "name": "_delegate", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountSD", + "type": "uint256" + } + ], + "name": "AmountSDOverflowed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "allowance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientAllowance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "ERC20InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC20InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "ERC20InvalidSender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "ERC20InvalidSpender", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidDelegate", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidEndpointCall", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidLocalDecimals", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "options", + "type": "bytes" + } + ], + "name": "InvalidOptions", + "type": "error" + }, + { + "inputs": [], + "name": "LzTokenUnavailable", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "eid", + "type": "uint32" + } + ], + "name": "NoPeer", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "msgValue", + "type": "uint256" + } + ], + "name": "NotEnoughNative", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "OnlyEndpoint", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "eid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + } + ], + "name": "OnlyPeer", + "type": "error" + }, + { + "inputs": [], + "name": "OnlySelf", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "result", + "type": "bytes" + } + ], + "name": "SimulationResult", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountLD", + "type": "uint256" + } + ], + "name": "SlippageExceeded", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "eid", + "type": "uint32" + }, + { + "internalType": "uint16", + "name": "msgType", + "type": "uint16" + }, + { + "internalType": "bytes", + "name": "options", + "type": "bytes" + } + ], + "indexed": false, + "internalType": "struct EnforcedOptionParam[]", + "name": "_enforcedOptions", + "type": "tuple[]" + } + ], + "name": "EnforcedOptionSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "inspector", + "type": "address" + } + ], + "name": "MsgInspectorSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "guid", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "srcEid", + "type": "uint32" + }, + { + "indexed": true, + "internalType": "address", + "name": "toAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountReceivedLD", + "type": "uint256" + } + ], + "name": "OFTReceived", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "guid", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "dstEid", + "type": "uint32" + }, + { + "indexed": true, + "internalType": "address", + "name": "fromAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountSentLD", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountReceivedLD", + "type": "uint256" + } + ], + "name": "OFTSent", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint32", + "name": "eid", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "peer", + "type": "bytes32" + } + ], + "name": "PeerSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "preCrimeAddress", + "type": "address" + } + ], + "name": "PreCrimeSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "SEND", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SEND_AND_CALL", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "srcEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "internalType": "struct Origin", + "name": "origin", + "type": "tuple" + } + ], + "name": "allowInitializePath", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "approvalRequired", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_eid", + "type": "uint32" + }, + { + "internalType": "uint16", + "name": "_msgType", + "type": "uint16" + }, + { + "internalType": "bytes", + "name": "_extraOptions", + "type": "bytes" + } + ], + "name": "combineOptions", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimalConversionRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "endpoint", + "outputs": [ + { + "internalType": "contract ILayerZeroEndpointV2", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "eid", + "type": "uint32" + }, + { + "internalType": "uint16", + "name": "msgType", + "type": "uint16" + } + ], + "name": "enforcedOptions", + "outputs": [ + { + "internalType": "bytes", + "name": "enforcedOption", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "srcEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "internalType": "struct Origin", + "name": "", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_sender", + "type": "address" + } + ], + "name": "isComposeMsgSender", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_eid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "_peer", + "type": "bytes32" + } + ], + "name": "isPeer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "srcEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "internalType": "struct Origin", + "name": "_origin", + "type": "tuple" + }, + { + "internalType": "bytes32", + "name": "_guid", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "_message", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_executor", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_extraData", + "type": "bytes" + } + ], + "name": "lzReceive", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "uint32", + "name": "srcEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "internalType": "struct Origin", + "name": "origin", + "type": "tuple" + }, + { + "internalType": "uint32", + "name": "dstEid", + "type": "uint32" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "guid", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "address", + "name": "executor", + "type": "address" + }, + { + "internalType": "bytes", + "name": "message", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "internalType": "struct InboundPacket[]", + "name": "_packets", + "type": "tuple[]" + } + ], + "name": "lzReceiveAndRevert", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "srcEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "internalType": "struct Origin", + "name": "_origin", + "type": "tuple" + }, + { + "internalType": "bytes32", + "name": "_guid", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "_message", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_executor", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_extraData", + "type": "bytes" + } + ], + "name": "lzReceiveSimulate", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "msgInspector", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "nextNonce", + "outputs": [ + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oApp", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oAppVersion", + "outputs": [ + { + "internalType": "uint64", + "name": "senderVersion", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "receiverVersion", + "type": "uint64" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "oftVersion", + "outputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + }, + { + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "eid", + "type": "uint32" + } + ], + "name": "peers", + "outputs": [ + { + "internalType": "bytes32", + "name": "peer", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "preCrime", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "dstEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "to", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amountLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountLD", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "extraOptions", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "composeMsg", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "oftCmd", + "type": "bytes" + } + ], + "internalType": "struct SendParam", + "name": "_sendParam", + "type": "tuple" + } + ], + "name": "quoteOFT", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "minAmountLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxAmountLD", + "type": "uint256" + } + ], + "internalType": "struct OFTLimit", + "name": "oftLimit", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "int256", + "name": "feeAmountLD", + "type": "int256" + }, + { + "internalType": "string", + "name": "description", + "type": "string" + } + ], + "internalType": "struct OFTFeeDetail[]", + "name": "oftFeeDetails", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "amountSentLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountReceivedLD", + "type": "uint256" + } + ], + "internalType": "struct OFTReceipt", + "name": "oftReceipt", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "dstEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "to", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amountLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountLD", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "extraOptions", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "composeMsg", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "oftCmd", + "type": "bytes" + } + ], + "internalType": "struct SendParam", + "name": "_sendParam", + "type": "tuple" + }, + { + "internalType": "bool", + "name": "_payInLzToken", + "type": "bool" + } + ], + "name": "quoteSend", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "nativeFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lzTokenFee", + "type": "uint256" + } + ], + "internalType": "struct MessagingFee", + "name": "msgFee", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "dstEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "to", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amountLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountLD", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "extraOptions", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "composeMsg", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "oftCmd", + "type": "bytes" + } + ], + "internalType": "struct SendParam", + "name": "_sendParam", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "nativeFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lzTokenFee", + "type": "uint256" + } + ], + "internalType": "struct MessagingFee", + "name": "_fee", + "type": "tuple" + }, + { + "internalType": "address", + "name": "_refundAddress", + "type": "address" + } + ], + "name": "send", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "guid", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "nativeFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lzTokenFee", + "type": "uint256" + } + ], + "internalType": "struct MessagingFee", + "name": "fee", + "type": "tuple" + } + ], + "internalType": "struct MessagingReceipt", + "name": "msgReceipt", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "amountSentLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountReceivedLD", + "type": "uint256" + } + ], + "internalType": "struct OFTReceipt", + "name": "oftReceipt", + "type": "tuple" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_delegate", + "type": "address" + } + ], + "name": "setDelegate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "eid", + "type": "uint32" + }, + { + "internalType": "uint16", + "name": "msgType", + "type": "uint16" + }, + { + "internalType": "bytes", + "name": "options", + "type": "bytes" + } + ], + "internalType": "struct EnforcedOptionParam[]", + "name": "_enforcedOptions", + "type": "tuple[]" + } + ], + "name": "setEnforcedOptions", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_msgInspector", + "type": "address" + } + ], + "name": "setMsgInspector", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_eid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "_peer", + "type": "bytes32" + } + ], + "name": "setPeer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_preCrime", + "type": "address" + } + ], + "name": "setPreCrime", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "sharedDecimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "transactionHash": "0xda357e4f6cce3144e22341d2ac6dffae2bddf534ae3707c609068c450c80087f", + "receipt": { + "to": null, + "from": "0x8583894d0e57e42abb83039537f314490038efa0", + "contractAddress": "0xcE65144C75d77c479b7FF12Cff41a1AC5359A578", + "transactionIndex": 6, + "gasUsed": "2887152", + "logsBloom": "0x00000000000000000000000000000000000000000000000000800000000000000000000000000000220000000000000000010002000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000100020000000200000000000800000000000000000000000000000000400000000000000020000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000040000000000000000400000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xda357e4f6cce3144e22341d2ac6dffae2bddf534ae3707c609068c450c80087f", + "logs": [ + { + "transactionIndex": 6, + "blockNumber": 44882549, + "transactionHash": "0xda357e4f6cce3144e22341d2ac6dffae2bddf534ae3707c609068c450c80087f", + "address": "0xcE65144C75d77c479b7FF12Cff41a1AC5359A578", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000008583894d0e57e42abb83039537f314490038efa0" + ], + "data": "0x", + "logIndex": 14, + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "transactionIndex": 6, + "blockNumber": 44882549, + "transactionHash": "0xda357e4f6cce3144e22341d2ac6dffae2bddf534ae3707c609068c450c80087f", + "address": "0x6EDCE65403992e310A62460808c4b910D972f10f", + "topics": [ + "0x6ee10e9ed4d6ce9742703a498707862f4b00f1396a87195eb93267b3d7983981" + ], + "data": "0x000000000000000000000000ce65144c75d77c479b7ff12cff41a1ac5359a5780000000000000000000000008583894d0e57e42abb83039537f314490038efa0", + "logIndex": 15, + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + ], + "blockNumber": 44882549, + "cumulativeGasUsed": "3338132", + "status": 1, + "byzantium": true + }, + "args": [ + "USD Coin", + "USDC", + "0x6EDCE65403992e310A62460808c4b910D972f10f", + "0x8583894d0e57e42abb83039537f314490038efa0" + ], + "numDeployments": 1, + "solcInputHash": "8088d7b064191499b181ffda0ed40a97", + "metadata": "{\"compiler\":{\"version\":\"0.8.22+commit.4fc1097e\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_name\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"_symbol\",\"type\":\"string\"},{\"internalType\":\"address\",\"name\":\"_lzEndpoint\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_delegate\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amountSD\",\"type\":\"uint256\"}],\"name\":\"AmountSDOverflowed\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"allowance\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"needed\",\"type\":\"uint256\"}],\"name\":\"ERC20InsufficientAllowance\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"needed\",\"type\":\"uint256\"}],\"name\":\"ERC20InsufficientBalance\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"approver\",\"type\":\"address\"}],\"name\":\"ERC20InvalidApprover\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"}],\"name\":\"ERC20InvalidReceiver\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"ERC20InvalidSender\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"ERC20InvalidSpender\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidDelegate\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidEndpointCall\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidLocalDecimals\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"options\",\"type\":\"bytes\"}],\"name\":\"InvalidOptions\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"LzTokenUnavailable\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"}],\"name\":\"NoPeer\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"msgValue\",\"type\":\"uint256\"}],\"name\":\"NotEnoughNative\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"OnlyEndpoint\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"}],\"name\":\"OnlyPeer\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlySelf\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"OwnableInvalidOwner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"OwnableUnauthorizedAccount\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"}],\"name\":\"SafeERC20FailedOperation\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"result\",\"type\":\"bytes\"}],\"name\":\"SimulationResult\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amountLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minAmountLD\",\"type\":\"uint256\"}],\"name\":\"SlippageExceeded\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"msgType\",\"type\":\"uint16\"},{\"internalType\":\"bytes\",\"name\":\"options\",\"type\":\"bytes\"}],\"indexed\":false,\"internalType\":\"struct EnforcedOptionParam[]\",\"name\":\"_enforcedOptions\",\"type\":\"tuple[]\"}],\"name\":\"EnforcedOptionSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"inspector\",\"type\":\"address\"}],\"name\":\"MsgInspectorSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"guid\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"srcEid\",\"type\":\"uint32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"toAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amountReceivedLD\",\"type\":\"uint256\"}],\"name\":\"OFTReceived\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"guid\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"fromAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amountSentLD\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amountReceivedLD\",\"type\":\"uint256\"}],\"name\":\"OFTSent\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"peer\",\"type\":\"bytes32\"}],\"name\":\"PeerSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"preCrimeAddress\",\"type\":\"address\"}],\"name\":\"PreCrimeSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"SEND\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"\",\"type\":\"uint16\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"SEND_AND_CALL\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"\",\"type\":\"uint16\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"srcEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"struct Origin\",\"name\":\"origin\",\"type\":\"tuple\"}],\"name\":\"allowInitializePath\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"approvalRequired\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"_eid\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"_msgType\",\"type\":\"uint16\"},{\"internalType\":\"bytes\",\"name\":\"_extraOptions\",\"type\":\"bytes\"}],\"name\":\"combineOptions\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimalConversionRate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"endpoint\",\"outputs\":[{\"internalType\":\"contract ILayerZeroEndpointV2\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"msgType\",\"type\":\"uint16\"}],\"name\":\"enforcedOptions\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"enforcedOption\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"srcEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"struct Origin\",\"name\":\"\",\"type\":\"tuple\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"_sender\",\"type\":\"address\"}],\"name\":\"isComposeMsgSender\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"_eid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"_peer\",\"type\":\"bytes32\"}],\"name\":\"isPeer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"srcEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"struct Origin\",\"name\":\"_origin\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"_guid\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"_message\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"_executor\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_extraData\",\"type\":\"bytes\"}],\"name\":\"lzReceive\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"srcEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"struct Origin\",\"name\":\"origin\",\"type\":\"tuple\"},{\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"guid\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"executor\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"message\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"extraData\",\"type\":\"bytes\"}],\"internalType\":\"struct InboundPacket[]\",\"name\":\"_packets\",\"type\":\"tuple[]\"}],\"name\":\"lzReceiveAndRevert\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"srcEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"struct Origin\",\"name\":\"_origin\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"_guid\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"_message\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"_executor\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_extraData\",\"type\":\"bytes\"}],\"name\":\"lzReceiveSimulate\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"mint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"msgInspector\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"nextNonce\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"oApp\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"oAppVersion\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"senderVersion\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"receiverVersion\",\"type\":\"uint64\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"oftVersion\",\"outputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"},{\"internalType\":\"uint64\",\"name\":\"version\",\"type\":\"uint64\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"}],\"name\":\"peers\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"peer\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"preCrime\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"to\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"amountLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minAmountLD\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"extraOptions\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"composeMsg\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"oftCmd\",\"type\":\"bytes\"}],\"internalType\":\"struct SendParam\",\"name\":\"_sendParam\",\"type\":\"tuple\"}],\"name\":\"quoteOFT\",\"outputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"minAmountLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxAmountLD\",\"type\":\"uint256\"}],\"internalType\":\"struct OFTLimit\",\"name\":\"oftLimit\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"int256\",\"name\":\"feeAmountLD\",\"type\":\"int256\"},{\"internalType\":\"string\",\"name\":\"description\",\"type\":\"string\"}],\"internalType\":\"struct OFTFeeDetail[]\",\"name\":\"oftFeeDetails\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"amountSentLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountReceivedLD\",\"type\":\"uint256\"}],\"internalType\":\"struct OFTReceipt\",\"name\":\"oftReceipt\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"to\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"amountLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minAmountLD\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"extraOptions\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"composeMsg\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"oftCmd\",\"type\":\"bytes\"}],\"internalType\":\"struct SendParam\",\"name\":\"_sendParam\",\"type\":\"tuple\"},{\"internalType\":\"bool\",\"name\":\"_payInLzToken\",\"type\":\"bool\"}],\"name\":\"quoteSend\",\"outputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"nativeFee\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"lzTokenFee\",\"type\":\"uint256\"}],\"internalType\":\"struct MessagingFee\",\"name\":\"msgFee\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"to\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"amountLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minAmountLD\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"extraOptions\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"composeMsg\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"oftCmd\",\"type\":\"bytes\"}],\"internalType\":\"struct SendParam\",\"name\":\"_sendParam\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"nativeFee\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"lzTokenFee\",\"type\":\"uint256\"}],\"internalType\":\"struct MessagingFee\",\"name\":\"_fee\",\"type\":\"tuple\"},{\"internalType\":\"address\",\"name\":\"_refundAddress\",\"type\":\"address\"}],\"name\":\"send\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"guid\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"nativeFee\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"lzTokenFee\",\"type\":\"uint256\"}],\"internalType\":\"struct MessagingFee\",\"name\":\"fee\",\"type\":\"tuple\"}],\"internalType\":\"struct MessagingReceipt\",\"name\":\"msgReceipt\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"amountSentLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountReceivedLD\",\"type\":\"uint256\"}],\"internalType\":\"struct OFTReceipt\",\"name\":\"oftReceipt\",\"type\":\"tuple\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_delegate\",\"type\":\"address\"}],\"name\":\"setDelegate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"msgType\",\"type\":\"uint16\"},{\"internalType\":\"bytes\",\"name\":\"options\",\"type\":\"bytes\"}],\"internalType\":\"struct EnforcedOptionParam[]\",\"name\":\"_enforcedOptions\",\"type\":\"tuple[]\"}],\"name\":\"setEnforcedOptions\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_msgInspector\",\"type\":\"address\"}],\"name\":\"setMsgInspector\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"_eid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"_peer\",\"type\":\"bytes32\"}],\"name\":\"setPeer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_preCrime\",\"type\":\"address\"}],\"name\":\"setPreCrime\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"sharedDecimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"errors\":{\"ERC20InsufficientAllowance(address,uint256,uint256)\":[{\"details\":\"Indicates a failure with the `spender`\\u2019s `allowance`. Used in transfers.\",\"params\":{\"allowance\":\"Amount of tokens a `spender` is allowed to operate with.\",\"needed\":\"Minimum amount required to perform a transfer.\",\"spender\":\"Address that may be allowed to operate on tokens without being their owner.\"}}],\"ERC20InsufficientBalance(address,uint256,uint256)\":[{\"details\":\"Indicates an error related to the current `balance` of a `sender`. Used in transfers.\",\"params\":{\"balance\":\"Current balance for the interacting account.\",\"needed\":\"Minimum amount required to perform a transfer.\",\"sender\":\"Address whose tokens are being transferred.\"}}],\"ERC20InvalidApprover(address)\":[{\"details\":\"Indicates a failure with the `approver` of a token to be approved. Used in approvals.\",\"params\":{\"approver\":\"Address initiating an approval operation.\"}}],\"ERC20InvalidReceiver(address)\":[{\"details\":\"Indicates a failure with the token `receiver`. Used in transfers.\",\"params\":{\"receiver\":\"Address to which tokens are being transferred.\"}}],\"ERC20InvalidSender(address)\":[{\"details\":\"Indicates a failure with the token `sender`. Used in transfers.\",\"params\":{\"sender\":\"Address whose tokens are being transferred.\"}}],\"ERC20InvalidSpender(address)\":[{\"details\":\"Indicates a failure with the `spender` to be approved. Used in approvals.\",\"params\":{\"spender\":\"Address that may be allowed to operate on tokens without being their owner.\"}}],\"OwnableInvalidOwner(address)\":[{\"details\":\"The owner is not a valid owner account. (eg. `address(0)`)\"}],\"OwnableUnauthorizedAccount(address)\":[{\"details\":\"The caller account is not authorized to perform an operation.\"}],\"SafeERC20FailedOperation(address)\":[{\"details\":\"An operation with an ERC-20 token failed.\"}]},\"events\":{\"Approval(address,address,uint256)\":{\"details\":\"Emitted when the allowance of a `spender` for an `owner` is set by a call to {approve}. `value` is the new allowance.\"},\"PreCrimeSet(address)\":{\"details\":\"Emitted when the preCrime contract address is set.\",\"params\":{\"preCrimeAddress\":\"The address of the preCrime contract.\"}},\"Transfer(address,address,uint256)\":{\"details\":\"Emitted when `value` tokens are moved from one account (`from`) to another (`to`). Note that `value` may be zero.\"}},\"kind\":\"dev\",\"methods\":{\"allowInitializePath((uint32,bytes32,uint64))\":{\"details\":\"This indicates to the endpoint that the OApp has enabled msgs for this particular path to be received.This defaults to assuming if a peer has been set, its initialized. Can be overridden by the OApp if there is other logic to determine this.\",\"params\":{\"origin\":\"The origin information containing the source endpoint and sender address.\"},\"returns\":{\"_0\":\"Whether the path has been initialized.\"}},\"allowance(address,address)\":{\"details\":\"Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` through {transferFrom}. This is zero by default. This value changes when {approve} or {transferFrom} are called.\"},\"approvalRequired()\":{\"details\":\"In the case of OFT where the contract IS the token, approval is NOT required.\",\"returns\":{\"_0\":\"requiresApproval Needs approval of the underlying token implementation.\"}},\"approve(address,uint256)\":{\"details\":\"See {IERC20-approve}. NOTE: If `value` is the maximum `uint256`, the allowance is not updated on `transferFrom`. This is semantically equivalent to an infinite approval. Requirements: - `spender` cannot be the zero address.\"},\"balanceOf(address)\":{\"details\":\"Returns the value of tokens owned by `account`.\"},\"combineOptions(uint32,uint16,bytes)\":{\"details\":\"If there is an enforced lzReceive option: - {gasLimit: 200k, msg.value: 1 ether} AND a caller supplies a lzReceive option: {gasLimit: 100k, msg.value: 0.5 ether} - The resulting options will be {gasLimit: 300k, msg.value: 1.5 ether} when the message is executed on the remote lzReceive() function.This presence of duplicated options is handled off-chain in the verifier/executor.\",\"params\":{\"_eid\":\"The endpoint ID.\",\"_extraOptions\":\"Additional options passed by the caller.\",\"_msgType\":\"The OAPP message type.\"},\"returns\":{\"_0\":\"options The combination of caller specified options AND enforced options.\"}},\"decimals()\":{\"details\":\"Returns the number of decimals used to get its user representation. For example, if `decimals` equals `2`, a balance of `505` tokens should be displayed to a user as `5.05` (`505 / 10 ** 2`). Tokens usually opt for a value of 18, imitating the relationship between Ether and Wei. This is the default value returned by this function, unless it's overridden. NOTE: This information is only used for _display_ purposes: it in no way affects any of the arithmetic of the contract, including {IERC20-balanceOf} and {IERC20-transfer}.\"},\"isComposeMsgSender((uint32,bytes32,uint64),bytes,address)\":{\"details\":\"_origin The origin information containing the source endpoint and sender address. - srcEid: The source chain endpoint ID. - sender: The sender address on the src chain. - nonce: The nonce of the message._message The lzReceive payload.Applications can optionally choose to implement separate composeMsg senders that are NOT the bridging layer.The default sender IS the OAppReceiver implementer.\",\"params\":{\"_sender\":\"The sender address.\"},\"returns\":{\"_0\":\"isSender Is a valid sender.\"}},\"isPeer(uint32,bytes32)\":{\"details\":\"Check if the peer is considered 'trusted' by the OApp.Enables OAppPreCrimeSimulator to check whether a potential Inbound Packet is from a trusted source.\",\"params\":{\"_eid\":\"The endpoint ID to check.\",\"_peer\":\"The peer to check.\"},\"returns\":{\"_0\":\"Whether the peer passed is considered 'trusted' by the OApp.\"}},\"lzReceive((uint32,bytes32,uint64),bytes32,bytes,address,bytes)\":{\"details\":\"Entry point for receiving messages or packets from the endpoint.Entry point for receiving msg/packet from the LayerZero endpoint.\",\"params\":{\"_executor\":\"The address of the executor for the received message.\",\"_extraData\":\"Additional arbitrary data provided by the corresponding executor.\",\"_guid\":\"The unique identifier for the received LayerZero message.\",\"_message\":\"The payload of the received message.\",\"_origin\":\"The origin information containing the source endpoint and sender address. - srcEid: The source chain endpoint ID. - sender: The sender address on the src chain. - nonce: The nonce of the message.\"}},\"lzReceiveAndRevert(((uint32,bytes32,uint64),uint32,address,bytes32,uint256,address,bytes,bytes)[])\":{\"details\":\"Interface for pre-crime simulations. Always reverts at the end with the simulation results.WARNING: MUST revert at the end with the simulation results.Gives the preCrime implementation the ability to mock sending packets to the lzReceive function, WITHOUT actually executing them.\",\"params\":{\"_packets\":\"An array of InboundPacket objects representing received packets to be delivered.\"}},\"lzReceiveSimulate((uint32,bytes32,uint64),bytes32,bytes,address,bytes)\":{\"details\":\"Is effectively an internal function because msg.sender must be address(this). Allows resetting the call stack for 'internal' calls.\",\"params\":{\"_executor\":\"The executor address for the packet.\",\"_extraData\":\"Additional data for the packet.\",\"_guid\":\"The unique identifier of the packet.\",\"_message\":\"The message payload of the packet.\",\"_origin\":\"The origin information containing the source endpoint and sender address. - srcEid: The source chain endpoint ID. - sender: The sender address on the src chain. - nonce: The nonce of the message.\"}},\"mint(address,uint256)\":{\"details\":\"Deliberately unguarded: the demo mints to itself before each send, and a testnet OFT with no way to obtain tokens cannot be used to exercise the DVN at all. Do NOT ship this to a network where the token has value. `virtual` because MyOFTMock declares the same function for the hardhat tests.\"},\"name()\":{\"details\":\"Returns the name of the token.\"},\"nextNonce(uint32,bytes32)\":{\"details\":\"_srcEid The source endpoint ID._sender The sender address.The path nonce starts from 1. If 0 is returned it means that there is NO nonce ordered enforcement.Is required by the off-chain executor to determine the OApp expects msg execution is ordered.This is also enforced by the OApp.By default this is NOT enabled. ie. nextNonce is hardcoded to return 0.\",\"returns\":{\"nonce\":\"The next nonce.\"}},\"oApp()\":{\"details\":\"Retrieves the address of the OApp contract.The simulator contract is the base contract for the OApp by default.If the simulator is a separate contract, override this function.\",\"returns\":{\"_0\":\"The address of the OApp contract.\"}},\"oAppVersion()\":{\"returns\":{\"receiverVersion\":\"The version of the OAppReceiver.sol implementation.\",\"senderVersion\":\"The version of the OAppSender.sol implementation.\"}},\"oftVersion()\":{\"details\":\"interfaceId: This specific interface ID is '0x02e49c2c'.version: Indicates a cross-chain compatible msg encoding with other OFTs.If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented. ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)\",\"returns\":{\"interfaceId\":\"The interface ID.\",\"version\":\"The version.\"}},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"quoteOFT((uint32,bytes32,uint256,uint256,bytes,bytes,bytes))\":{\"params\":{\"_sendParam\":\"The parameters for the send operation.\"},\"returns\":{\"oftFeeDetails\":\"The details of OFT fees.\",\"oftLimit\":\"The OFT limit information.\",\"oftReceipt\":\"The OFT receipt information.\"}},\"quoteSend((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),bool)\":{\"details\":\"MessagingFee: LayerZero msg fee - nativeFee: The native fee. - lzTokenFee: The lzToken fee.\",\"params\":{\"_payInLzToken\":\"Flag indicating whether the caller is paying in the LZ token.\",\"_sendParam\":\"The parameters for the send() operation.\"},\"returns\":{\"msgFee\":\"The calculated LayerZero messaging fee from the send() operation.\"}},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner.\"},\"send((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),(uint256,uint256),address)\":{\"details\":\"Executes the send operation.MessagingReceipt: LayerZero msg receipt - guid: The unique identifier for the sent message. - nonce: The nonce of the sent message. - fee: The LayerZero fee incurred for the message.\",\"params\":{\"_fee\":\"The calculated fee for the send() operation. - nativeFee: The native fee. - lzTokenFee: The lzToken fee.\",\"_refundAddress\":\"The address to receive any excess funds.\",\"_sendParam\":\"The parameters for the send operation.\"},\"returns\":{\"msgReceipt\":\"The receipt for the send operation.\",\"oftReceipt\":\"The OFT receipt information.\"}},\"setDelegate(address)\":{\"details\":\"Only the owner/admin of the OApp can call this function.Provides the ability for a delegate to set configs, on behalf of the OApp, directly on the Endpoint contract.\",\"params\":{\"_delegate\":\"The address of the delegate to be set.\"}},\"setEnforcedOptions((uint32,uint16,bytes)[])\":{\"details\":\"Sets the enforced options for specific endpoint and message type combinations.Only the owner/admin of the OApp can call this function.Provides a way for the OApp to enforce things like paying for PreCrime, AND/OR minimum dst lzReceive gas amounts etc.These enforced options can vary as the potential options/execution on the remote may differ as per the msgType. eg. Amount of lzReceive() gas necessary to deliver a lzCompose() message adds overhead you dont want to pay if you are only making a standard LayerZero message ie. lzReceive() WITHOUT sendCompose().\",\"params\":{\"_enforcedOptions\":\"An array of EnforcedOptionParam structures specifying enforced options.\"}},\"setMsgInspector(address)\":{\"details\":\"Sets the message inspector address for the OFT.This is an optional contract that can be used to inspect both 'message' and 'options'.Set it to address(0) to disable it, or set it to a contract address to enable it.\",\"params\":{\"_msgInspector\":\"The address of the message inspector.\"}},\"setPeer(uint32,bytes32)\":{\"details\":\"Only the owner/admin of the OApp can call this function.Indicates that the peer is trusted to send LayerZero messages to this OApp.Set this to bytes32(0) to remove the peer address.Peer is a bytes32 to accommodate non-evm chains.\",\"params\":{\"_eid\":\"The endpoint ID.\",\"_peer\":\"The address of the peer to be associated with the corresponding endpoint.\"}},\"setPreCrime(address)\":{\"details\":\"Sets the preCrime contract address.\",\"params\":{\"_preCrime\":\"The address of the preCrime contract.\"}},\"sharedDecimals()\":{\"details\":\"Retrieves the shared decimals of the OFT.Sets an implicit cap on the amount of tokens, over uint64.max() will need some sort of outbound cap / totalSupply cap Lowest common decimal denominator between chains. Defaults to 6 decimal places to provide up to 18,446,744,073,709.551615 units (max uint64). For tokens exceeding this totalSupply(), they will need to override the sharedDecimals function with something smaller. ie. 4 sharedDecimals would be 1,844,674,407,370,955.1615\",\"returns\":{\"_0\":\"The shared decimals of the OFT.\"}},\"symbol()\":{\"details\":\"Returns the symbol of the token, usually a shorter version of the name.\"},\"token()\":{\"details\":\"Retrieves the address of the underlying ERC20 implementation.In the case of OFT, address(this) and erc20 are the same contract.\",\"returns\":{\"_0\":\"The address of the OFT token.\"}},\"totalSupply()\":{\"details\":\"Returns the value of tokens in existence.\"},\"transfer(address,uint256)\":{\"details\":\"See {IERC20-transfer}. Requirements: - `to` cannot be the zero address. - the caller must have a balance of at least `value`.\"},\"transferFrom(address,address,uint256)\":{\"details\":\"See {IERC20-transferFrom}. Skips emitting an {Approval} event indicating an allowance update. This is not required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve]. NOTE: Does not update the allowance if the current allowance is the maximum `uint256`. Requirements: - `from` and `to` cannot be the zero address. - `from` must have a balance of at least `value`. - the caller must have allowance for ``from``'s tokens of at least `value`.\"},\"transferOwnership(address)\":{\"details\":\"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"allowInitializePath((uint32,bytes32,uint64))\":{\"notice\":\"Checks if the path initialization is allowed based on the provided origin.\"},\"approvalRequired()\":{\"notice\":\"Indicates whether the OFT contract requires approval of the 'token()' to send.\"},\"combineOptions(uint32,uint16,bytes)\":{\"notice\":\"Combines options for a given endpoint and message type.\"},\"endpoint()\":{\"notice\":\"Retrieves the LayerZero endpoint associated with the OApp.\"},\"isComposeMsgSender((uint32,bytes32,uint64),bytes,address)\":{\"notice\":\"Indicates whether an address is an approved composeMsg sender to the Endpoint.\"},\"mint(address,uint256)\":{\"notice\":\"Open mint, TESTNET ONLY \\u2014 same as ToyOFT, so the demo tasks work against either.\"},\"nextNonce(uint32,bytes32)\":{\"notice\":\"Retrieves the next nonce for a given source endpoint and sender address.\"},\"oAppVersion()\":{\"notice\":\"Retrieves the OApp version information.\"},\"oftVersion()\":{\"notice\":\"Retrieves interfaceID and the version of the OFT.\"},\"peers(uint32)\":{\"notice\":\"Retrieves the peer (OApp) associated with a corresponding endpoint.\"},\"quoteOFT((uint32,bytes32,uint256,uint256,bytes,bytes,bytes))\":{\"notice\":\"Provides the fee breakdown and settings data for an OFT. Unused in the default implementation.\"},\"quoteSend((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),bool)\":{\"notice\":\"Provides a quote for the send() operation.\"},\"setDelegate(address)\":{\"notice\":\"Sets the delegate address for the OApp.\"},\"setPeer(uint32,bytes32)\":{\"notice\":\"Sets the peer address (OApp instance) for a corresponding endpoint.\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/MyOFT.sol\":\"MyOFT\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\nimport { IMessageLibManager } from \\\"./IMessageLibManager.sol\\\";\\nimport { IMessagingComposer } from \\\"./IMessagingComposer.sol\\\";\\nimport { IMessagingChannel } from \\\"./IMessagingChannel.sol\\\";\\nimport { IMessagingContext } from \\\"./IMessagingContext.sol\\\";\\n\\nstruct MessagingParams {\\n uint32 dstEid;\\n bytes32 receiver;\\n bytes message;\\n bytes options;\\n bool payInLzToken;\\n}\\n\\nstruct MessagingReceipt {\\n bytes32 guid;\\n uint64 nonce;\\n MessagingFee fee;\\n}\\n\\nstruct MessagingFee {\\n uint256 nativeFee;\\n uint256 lzTokenFee;\\n}\\n\\nstruct Origin {\\n uint32 srcEid;\\n bytes32 sender;\\n uint64 nonce;\\n}\\n\\ninterface ILayerZeroEndpointV2 is IMessageLibManager, IMessagingComposer, IMessagingChannel, IMessagingContext {\\n event PacketSent(bytes encodedPayload, bytes options, address sendLibrary);\\n\\n event PacketVerified(Origin origin, address receiver, bytes32 payloadHash);\\n\\n event PacketDelivered(Origin origin, address receiver);\\n\\n event LzReceiveAlert(\\n address indexed receiver,\\n address indexed executor,\\n Origin origin,\\n bytes32 guid,\\n uint256 gas,\\n uint256 value,\\n bytes message,\\n bytes extraData,\\n bytes reason\\n );\\n\\n event LzTokenSet(address token);\\n\\n event DelegateSet(address sender, address delegate);\\n\\n function quote(MessagingParams calldata _params, address _sender) external view returns (MessagingFee memory);\\n\\n function send(\\n MessagingParams calldata _params,\\n address _refundAddress\\n ) external payable returns (MessagingReceipt memory);\\n\\n function verify(Origin calldata _origin, address _receiver, bytes32 _payloadHash) external;\\n\\n function verifiable(Origin calldata _origin, address _receiver) external view returns (bool);\\n\\n function initializable(Origin calldata _origin, address _receiver) external view returns (bool);\\n\\n function lzReceive(\\n Origin calldata _origin,\\n address _receiver,\\n bytes32 _guid,\\n bytes calldata _message,\\n bytes calldata _extraData\\n ) external payable;\\n\\n // oapp can burn messages partially by calling this function with its own business logic if messages are verified in order\\n function clear(address _oapp, Origin calldata _origin, bytes32 _guid, bytes calldata _message) external;\\n\\n function setLzToken(address _lzToken) external;\\n\\n function lzToken() external view returns (address);\\n\\n function nativeToken() external view returns (address);\\n\\n function setDelegate(address _delegate) external;\\n}\\n\",\"keccak256\":\"0xf7f941bee89ea6369950fe54e8ac476ae6478b958b20fc0e8a83e8ff1364eac3\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroReceiver.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\nimport { Origin } from \\\"./ILayerZeroEndpointV2.sol\\\";\\n\\ninterface ILayerZeroReceiver {\\n function allowInitializePath(Origin calldata _origin) external view returns (bool);\\n\\n function nextNonce(uint32 _eid, bytes32 _sender) external view returns (uint64);\\n\\n function lzReceive(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address _executor,\\n bytes calldata _extraData\\n ) external payable;\\n}\\n\",\"keccak256\":\"0x9641abba8d53b08bb517d1b74801dd15ea7b84d77a6719085bd96c8ea94e3ca0\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLib.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\nimport { IERC165 } from \\\"@openzeppelin/contracts/utils/introspection/IERC165.sol\\\";\\n\\nimport { SetConfigParam } from \\\"./IMessageLibManager.sol\\\";\\n\\nenum MessageLibType {\\n Send,\\n Receive,\\n SendAndReceive\\n}\\n\\ninterface IMessageLib is IERC165 {\\n function setConfig(address _oapp, SetConfigParam[] calldata _config) external;\\n\\n function getConfig(uint32 _eid, address _oapp, uint32 _configType) external view returns (bytes memory config);\\n\\n function isSupportedEid(uint32 _eid) external view returns (bool);\\n\\n // message libs of same major version are compatible\\n function version() external view returns (uint64 major, uint8 minor, uint8 endpointVersion);\\n\\n function messageLibType() external view returns (MessageLibType);\\n}\\n\",\"keccak256\":\"0x5cf5f24751b4e3ea1c9c5ded07cedfdfd62566b6daaffcc0144733859c9dba0c\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLibManager.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\nstruct SetConfigParam {\\n uint32 eid;\\n uint32 configType;\\n bytes config;\\n}\\n\\ninterface IMessageLibManager {\\n struct Timeout {\\n address lib;\\n uint256 expiry;\\n }\\n\\n event LibraryRegistered(address newLib);\\n event DefaultSendLibrarySet(uint32 eid, address newLib);\\n event DefaultReceiveLibrarySet(uint32 eid, address newLib);\\n event DefaultReceiveLibraryTimeoutSet(uint32 eid, address oldLib, uint256 expiry);\\n event SendLibrarySet(address sender, uint32 eid, address newLib);\\n event ReceiveLibrarySet(address receiver, uint32 eid, address newLib);\\n event ReceiveLibraryTimeoutSet(address receiver, uint32 eid, address oldLib, uint256 timeout);\\n\\n function registerLibrary(address _lib) external;\\n\\n function isRegisteredLibrary(address _lib) external view returns (bool);\\n\\n function getRegisteredLibraries() external view returns (address[] memory);\\n\\n function setDefaultSendLibrary(uint32 _eid, address _newLib) external;\\n\\n function defaultSendLibrary(uint32 _eid) external view returns (address);\\n\\n function setDefaultReceiveLibrary(uint32 _eid, address _newLib, uint256 _gracePeriod) external;\\n\\n function defaultReceiveLibrary(uint32 _eid) external view returns (address);\\n\\n function setDefaultReceiveLibraryTimeout(uint32 _eid, address _lib, uint256 _expiry) external;\\n\\n function defaultReceiveLibraryTimeout(uint32 _eid) external view returns (address lib, uint256 expiry);\\n\\n function isSupportedEid(uint32 _eid) external view returns (bool);\\n\\n function isValidReceiveLibrary(address _receiver, uint32 _eid, address _lib) external view returns (bool);\\n\\n /// ------------------- OApp interfaces -------------------\\n function setSendLibrary(address _oapp, uint32 _eid, address _newLib) external;\\n\\n function getSendLibrary(address _sender, uint32 _eid) external view returns (address lib);\\n\\n function isDefaultSendLibrary(address _sender, uint32 _eid) external view returns (bool);\\n\\n function setReceiveLibrary(address _oapp, uint32 _eid, address _newLib, uint256 _gracePeriod) external;\\n\\n function getReceiveLibrary(address _receiver, uint32 _eid) external view returns (address lib, bool isDefault);\\n\\n function setReceiveLibraryTimeout(address _oapp, uint32 _eid, address _lib, uint256 _expiry) external;\\n\\n function receiveLibraryTimeout(address _receiver, uint32 _eid) external view returns (address lib, uint256 expiry);\\n\\n function setConfig(address _oapp, address _lib, SetConfigParam[] calldata _params) external;\\n\\n function getConfig(\\n address _oapp,\\n address _lib,\\n uint32 _eid,\\n uint32 _configType\\n ) external view returns (bytes memory config);\\n}\\n\",\"keccak256\":\"0x919b37133adff4dc528e3061deb2789c3149971b530c61e556fb3d09ab315dfc\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingChannel.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\ninterface IMessagingChannel {\\n event InboundNonceSkipped(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce);\\n event PacketNilified(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce, bytes32 payloadHash);\\n event PacketBurnt(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce, bytes32 payloadHash);\\n\\n function eid() external view returns (uint32);\\n\\n // this is an emergency function if a message cannot be verified for some reasons\\n // required to provide _nextNonce to avoid race condition\\n function skip(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce) external;\\n\\n function nilify(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes32 _payloadHash) external;\\n\\n function burn(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes32 _payloadHash) external;\\n\\n function nextGuid(address _sender, uint32 _dstEid, bytes32 _receiver) external view returns (bytes32);\\n\\n function inboundNonce(address _receiver, uint32 _srcEid, bytes32 _sender) external view returns (uint64);\\n\\n function outboundNonce(address _sender, uint32 _dstEid, bytes32 _receiver) external view returns (uint64);\\n\\n function inboundPayloadHash(\\n address _receiver,\\n uint32 _srcEid,\\n bytes32 _sender,\\n uint64 _nonce\\n ) external view returns (bytes32);\\n\\n function lazyInboundNonce(address _receiver, uint32 _srcEid, bytes32 _sender) external view returns (uint64);\\n}\\n\",\"keccak256\":\"0x0878f64dffebf58c4165569416372f40860fab546b88cd926eba0d5cb6d8d972\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingComposer.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\ninterface IMessagingComposer {\\n event ComposeSent(address from, address to, bytes32 guid, uint16 index, bytes message);\\n event ComposeDelivered(address from, address to, bytes32 guid, uint16 index);\\n event LzComposeAlert(\\n address indexed from,\\n address indexed to,\\n address indexed executor,\\n bytes32 guid,\\n uint16 index,\\n uint256 gas,\\n uint256 value,\\n bytes message,\\n bytes extraData,\\n bytes reason\\n );\\n\\n function composeQueue(\\n address _from,\\n address _to,\\n bytes32 _guid,\\n uint16 _index\\n ) external view returns (bytes32 messageHash);\\n\\n function sendCompose(address _to, bytes32 _guid, uint16 _index, bytes calldata _message) external;\\n\\n function lzCompose(\\n address _from,\\n address _to,\\n bytes32 _guid,\\n uint16 _index,\\n bytes calldata _message,\\n bytes calldata _extraData\\n ) external payable;\\n}\\n\",\"keccak256\":\"0x85bc7090134529ec474866dc4bb1c48692d518c756eb0a961c82574829c51901\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingContext.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\ninterface IMessagingContext {\\n function isSendingMessage() external view returns (bool);\\n\\n function getSendContext() external view returns (uint32 dstEid, address sender);\\n}\\n\",\"keccak256\":\"0xff0c546c2813dae3e440882f46b377375f7461b0714efd80bd3f0c6e5cb8da4e\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ISendLib.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\nimport { MessagingFee } from \\\"./ILayerZeroEndpointV2.sol\\\";\\nimport { IMessageLib } from \\\"./IMessageLib.sol\\\";\\n\\nstruct Packet {\\n uint64 nonce;\\n uint32 srcEid;\\n address sender;\\n uint32 dstEid;\\n bytes32 receiver;\\n bytes32 guid;\\n bytes message;\\n}\\n\\ninterface ISendLib is IMessageLib {\\n function send(\\n Packet calldata _packet,\\n bytes calldata _options,\\n bool _payInLzToken\\n ) external returns (MessagingFee memory, bytes memory encodedPacket);\\n\\n function quote(\\n Packet calldata _packet,\\n bytes calldata _options,\\n bool _payInLzToken\\n ) external view returns (MessagingFee memory);\\n\\n function setTreasury(address _treasury) external;\\n\\n function withdrawFee(address _to, uint256 _amount) external;\\n\\n function withdrawLzTokenFee(address _lzToken, address _to, uint256 _amount) external;\\n}\\n\",\"keccak256\":\"0xf1c07bc61e7b1dce195ed12d50f87980fbf2d63cac1326fd28287f55fe0ba625\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/libs/AddressCast.sol\":{\"content\":\"// SPDX-License-Identifier: LZBL-1.2\\n\\npragma solidity ^0.8.20;\\n\\nlibrary AddressCast {\\n error AddressCast_InvalidSizeForAddress();\\n error AddressCast_InvalidAddress();\\n\\n function toBytes32(bytes calldata _addressBytes) internal pure returns (bytes32 result) {\\n if (_addressBytes.length > 32) revert AddressCast_InvalidAddress();\\n result = bytes32(_addressBytes);\\n unchecked {\\n uint256 offset = 32 - _addressBytes.length;\\n result = result >> (offset * 8);\\n }\\n }\\n\\n function toBytes32(address _address) internal pure returns (bytes32 result) {\\n result = bytes32(uint256(uint160(_address)));\\n }\\n\\n function toBytes(bytes32 _addressBytes32, uint256 _size) internal pure returns (bytes memory result) {\\n if (_size == 0 || _size > 32) revert AddressCast_InvalidSizeForAddress();\\n result = new bytes(_size);\\n unchecked {\\n uint256 offset = 256 - _size * 8;\\n assembly {\\n mstore(add(result, 32), shl(offset, _addressBytes32))\\n }\\n }\\n }\\n\\n function toAddress(bytes32 _addressBytes32) internal pure returns (address result) {\\n result = address(uint160(uint256(_addressBytes32)));\\n }\\n\\n function toAddress(bytes calldata _addressBytes) internal pure returns (address result) {\\n if (_addressBytes.length != 20) revert AddressCast_InvalidAddress();\\n result = address(bytes20(_addressBytes));\\n }\\n}\\n\",\"keccak256\":\"0x2ebbcaaab3554edcd41b581f1a72ac1806afbfb8047d0d47ff098f9af30d6deb\",\"license\":\"LZBL-1.2\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.sol\":{\"content\":\"// SPDX-License-Identifier: LZBL-1.2\\n\\npragma solidity ^0.8.20;\\n\\nimport { Packet } from \\\"../../interfaces/ISendLib.sol\\\";\\nimport { AddressCast } from \\\"../../libs/AddressCast.sol\\\";\\n\\nlibrary PacketV1Codec {\\n using AddressCast for address;\\n using AddressCast for bytes32;\\n\\n uint8 internal constant PACKET_VERSION = 1;\\n\\n // header (version + nonce + path)\\n // version\\n uint256 private constant PACKET_VERSION_OFFSET = 0;\\n // nonce\\n uint256 private constant NONCE_OFFSET = 1;\\n // path\\n uint256 private constant SRC_EID_OFFSET = 9;\\n uint256 private constant SENDER_OFFSET = 13;\\n uint256 private constant DST_EID_OFFSET = 45;\\n uint256 private constant RECEIVER_OFFSET = 49;\\n // payload (guid + message)\\n uint256 private constant GUID_OFFSET = 81; // keccak256(nonce + path)\\n uint256 private constant MESSAGE_OFFSET = 113;\\n\\n function encode(Packet memory _packet) internal pure returns (bytes memory encodedPacket) {\\n encodedPacket = abi.encodePacked(\\n PACKET_VERSION,\\n _packet.nonce,\\n _packet.srcEid,\\n _packet.sender.toBytes32(),\\n _packet.dstEid,\\n _packet.receiver,\\n _packet.guid,\\n _packet.message\\n );\\n }\\n\\n function encodePacketHeader(Packet memory _packet) internal pure returns (bytes memory) {\\n return\\n abi.encodePacked(\\n PACKET_VERSION,\\n _packet.nonce,\\n _packet.srcEid,\\n _packet.sender.toBytes32(),\\n _packet.dstEid,\\n _packet.receiver\\n );\\n }\\n\\n function encodePayload(Packet memory _packet) internal pure returns (bytes memory) {\\n return abi.encodePacked(_packet.guid, _packet.message);\\n }\\n\\n function header(bytes calldata _packet) internal pure returns (bytes calldata) {\\n return _packet[0:GUID_OFFSET];\\n }\\n\\n function version(bytes calldata _packet) internal pure returns (uint8) {\\n return uint8(bytes1(_packet[PACKET_VERSION_OFFSET:NONCE_OFFSET]));\\n }\\n\\n function nonce(bytes calldata _packet) internal pure returns (uint64) {\\n return uint64(bytes8(_packet[NONCE_OFFSET:SRC_EID_OFFSET]));\\n }\\n\\n function srcEid(bytes calldata _packet) internal pure returns (uint32) {\\n return uint32(bytes4(_packet[SRC_EID_OFFSET:SENDER_OFFSET]));\\n }\\n\\n function sender(bytes calldata _packet) internal pure returns (bytes32) {\\n return bytes32(_packet[SENDER_OFFSET:DST_EID_OFFSET]);\\n }\\n\\n function senderAddressB20(bytes calldata _packet) internal pure returns (address) {\\n return sender(_packet).toAddress();\\n }\\n\\n function dstEid(bytes calldata _packet) internal pure returns (uint32) {\\n return uint32(bytes4(_packet[DST_EID_OFFSET:RECEIVER_OFFSET]));\\n }\\n\\n function receiver(bytes calldata _packet) internal pure returns (bytes32) {\\n return bytes32(_packet[RECEIVER_OFFSET:GUID_OFFSET]);\\n }\\n\\n function receiverB20(bytes calldata _packet) internal pure returns (address) {\\n return receiver(_packet).toAddress();\\n }\\n\\n function guid(bytes calldata _packet) internal pure returns (bytes32) {\\n return bytes32(_packet[GUID_OFFSET:MESSAGE_OFFSET]);\\n }\\n\\n function message(bytes calldata _packet) internal pure returns (bytes calldata) {\\n return bytes(_packet[MESSAGE_OFFSET:]);\\n }\\n\\n function payload(bytes calldata _packet) internal pure returns (bytes calldata) {\\n return bytes(_packet[GUID_OFFSET:]);\\n }\\n\\n function payloadHash(bytes calldata _packet) internal pure returns (bytes32) {\\n return keccak256(payload(_packet));\\n }\\n}\\n\",\"keccak256\":\"0xc84cf1bf785977fe1fbe7566eef902c2db68d0e163813ebe6c34921754802680\",\"license\":\"LZBL-1.2\"},\"@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\n// @dev Import the 'MessagingFee' and 'MessagingReceipt' so it's exposed to OApp implementers\\n// solhint-disable-next-line no-unused-import\\nimport { OAppSender, MessagingFee, MessagingReceipt } from \\\"./OAppSender.sol\\\";\\n// @dev Import the 'Origin' so it's exposed to OApp implementers\\n// solhint-disable-next-line no-unused-import\\nimport { OAppReceiver, Origin } from \\\"./OAppReceiver.sol\\\";\\nimport { OAppCore } from \\\"./OAppCore.sol\\\";\\n\\n/**\\n * @title OApp\\n * @dev Abstract contract serving as the base for OApp implementation, combining OAppSender and OAppReceiver functionality.\\n */\\nabstract contract OApp is OAppSender, OAppReceiver {\\n /**\\n * @dev Constructor to initialize the OApp with the provided endpoint and owner.\\n * @param _endpoint The address of the LOCAL LayerZero endpoint.\\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\\n */\\n constructor(address _endpoint, address _delegate) OAppCore(_endpoint, _delegate) {}\\n\\n /**\\n * @notice Retrieves the OApp version information.\\n * @return senderVersion The version of the OAppSender.sol implementation.\\n * @return receiverVersion The version of the OAppReceiver.sol implementation.\\n */\\n function oAppVersion()\\n public\\n pure\\n virtual\\n override(OAppSender, OAppReceiver)\\n returns (uint64 senderVersion, uint64 receiverVersion)\\n {\\n return (SENDER_VERSION, RECEIVER_VERSION);\\n }\\n}\\n\",\"keccak256\":\"0xac362c4c291fad2f1511a968424b2e78a5ad502d1c867bd31da04be742aca8c5\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/OAppCore.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { Ownable } from \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\nimport { IOAppCore, ILayerZeroEndpointV2 } from \\\"./interfaces/IOAppCore.sol\\\";\\n\\n/**\\n * @title OAppCore\\n * @dev Abstract contract implementing the IOAppCore interface with basic OApp configurations.\\n */\\nabstract contract OAppCore is IOAppCore, Ownable {\\n // The LayerZero endpoint associated with the given OApp\\n ILayerZeroEndpointV2 public immutable endpoint;\\n\\n // Mapping to store peers associated with corresponding endpoints\\n mapping(uint32 eid => bytes32 peer) public peers;\\n\\n /**\\n * @dev Constructor to initialize the OAppCore with the provided endpoint and delegate.\\n * @param _endpoint The address of the LOCAL Layer Zero endpoint.\\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\\n *\\n * @dev The delegate typically should be set as the owner of the contract.\\n */\\n constructor(address _endpoint, address _delegate) {\\n endpoint = ILayerZeroEndpointV2(_endpoint);\\n\\n if (_delegate == address(0)) revert InvalidDelegate();\\n endpoint.setDelegate(_delegate);\\n }\\n\\n /**\\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\\n * @param _eid The endpoint ID.\\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\\n *\\n * @dev Only the owner/admin of the OApp can call this function.\\n * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp.\\n * @dev Set this to bytes32(0) to remove the peer address.\\n * @dev Peer is a bytes32 to accommodate non-evm chains.\\n */\\n function setPeer(uint32 _eid, bytes32 _peer) public virtual onlyOwner {\\n _setPeer(_eid, _peer);\\n }\\n\\n /**\\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\\n * @param _eid The endpoint ID.\\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\\n *\\n * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp.\\n * @dev Set this to bytes32(0) to remove the peer address.\\n * @dev Peer is a bytes32 to accommodate non-evm chains.\\n */\\n function _setPeer(uint32 _eid, bytes32 _peer) internal virtual {\\n peers[_eid] = _peer;\\n emit PeerSet(_eid, _peer);\\n }\\n\\n /**\\n * @notice Internal function to get the peer address associated with a specific endpoint; reverts if NOT set.\\n * ie. the peer is set to bytes32(0).\\n * @param _eid The endpoint ID.\\n * @return peer The address of the peer associated with the specified endpoint.\\n */\\n function _getPeerOrRevert(uint32 _eid) internal view virtual returns (bytes32) {\\n bytes32 peer = peers[_eid];\\n if (peer == bytes32(0)) revert NoPeer(_eid);\\n return peer;\\n }\\n\\n /**\\n * @notice Sets the delegate address for the OApp.\\n * @param _delegate The address of the delegate to be set.\\n *\\n * @dev Only the owner/admin of the OApp can call this function.\\n * @dev Provides the ability for a delegate to set configs, on behalf of the OApp, directly on the Endpoint contract.\\n */\\n function setDelegate(address _delegate) public onlyOwner {\\n endpoint.setDelegate(_delegate);\\n }\\n}\\n\",\"keccak256\":\"0x13a9c2d1d2c1f086b8624f2e84c4a4702212daae36f701d92bb915b535cbe4cc\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/OAppReceiver.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { IOAppReceiver, Origin } from \\\"./interfaces/IOAppReceiver.sol\\\";\\nimport { OAppCore } from \\\"./OAppCore.sol\\\";\\n\\n/**\\n * @title OAppReceiver\\n * @dev Abstract contract implementing the ILayerZeroReceiver interface and extending OAppCore for OApp receivers.\\n */\\nabstract contract OAppReceiver is IOAppReceiver, OAppCore {\\n // Custom error message for when the caller is not the registered endpoint/\\n error OnlyEndpoint(address addr);\\n\\n // @dev The version of the OAppReceiver implementation.\\n // @dev Version is bumped when changes are made to this contract.\\n uint64 internal constant RECEIVER_VERSION = 2;\\n\\n /**\\n * @notice Retrieves the OApp version information.\\n * @return senderVersion The version of the OAppSender.sol contract.\\n * @return receiverVersion The version of the OAppReceiver.sol contract.\\n *\\n * @dev Providing 0 as the default for OAppSender version. Indicates that the OAppSender is not implemented.\\n * ie. this is a RECEIVE only OApp.\\n * @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions.\\n */\\n function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) {\\n return (0, RECEIVER_VERSION);\\n }\\n\\n /**\\n * @notice Indicates whether an address is an approved composeMsg sender to the Endpoint.\\n * @dev _origin The origin information containing the source endpoint and sender address.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address on the src chain.\\n * - nonce: The nonce of the message.\\n * @dev _message The lzReceive payload.\\n * @param _sender The sender address.\\n * @return isSender Is a valid sender.\\n *\\n * @dev Applications can optionally choose to implement separate composeMsg senders that are NOT the bridging layer.\\n * @dev The default sender IS the OAppReceiver implementer.\\n */\\n function isComposeMsgSender(\\n Origin calldata /*_origin*/,\\n bytes calldata /*_message*/,\\n address _sender\\n ) public view virtual returns (bool) {\\n return _sender == address(this);\\n }\\n\\n /**\\n * @notice Checks if the path initialization is allowed based on the provided origin.\\n * @param origin The origin information containing the source endpoint and sender address.\\n * @return Whether the path has been initialized.\\n *\\n * @dev This indicates to the endpoint that the OApp has enabled msgs for this particular path to be received.\\n * @dev This defaults to assuming if a peer has been set, its initialized.\\n * Can be overridden by the OApp if there is other logic to determine this.\\n */\\n function allowInitializePath(Origin calldata origin) public view virtual returns (bool) {\\n return peers[origin.srcEid] == origin.sender;\\n }\\n\\n /**\\n * @notice Retrieves the next nonce for a given source endpoint and sender address.\\n * @dev _srcEid The source endpoint ID.\\n * @dev _sender The sender address.\\n * @return nonce The next nonce.\\n *\\n * @dev The path nonce starts from 1. If 0 is returned it means that there is NO nonce ordered enforcement.\\n * @dev Is required by the off-chain executor to determine the OApp expects msg execution is ordered.\\n * @dev This is also enforced by the OApp.\\n * @dev By default this is NOT enabled. ie. nextNonce is hardcoded to return 0.\\n */\\n function nextNonce(uint32 /*_srcEid*/, bytes32 /*_sender*/) public view virtual returns (uint64 nonce) {\\n return 0;\\n }\\n\\n /**\\n * @dev Entry point for receiving messages or packets from the endpoint.\\n * @param _origin The origin information containing the source endpoint and sender address.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address on the src chain.\\n * - nonce: The nonce of the message.\\n * @param _guid The unique identifier for the received LayerZero message.\\n * @param _message The payload of the received message.\\n * @param _executor The address of the executor for the received message.\\n * @param _extraData Additional arbitrary data provided by the corresponding executor.\\n *\\n * @dev Entry point for receiving msg/packet from the LayerZero endpoint.\\n */\\n function lzReceive(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address _executor,\\n bytes calldata _extraData\\n ) public payable virtual {\\n // Ensures that only the endpoint can attempt to lzReceive() messages to this OApp.\\n if (address(endpoint) != msg.sender) revert OnlyEndpoint(msg.sender);\\n\\n // Ensure that the sender matches the expected peer for the source endpoint.\\n if (_getPeerOrRevert(_origin.srcEid) != _origin.sender) revert OnlyPeer(_origin.srcEid, _origin.sender);\\n\\n // Call the internal OApp implementation of lzReceive.\\n _lzReceive(_origin, _guid, _message, _executor, _extraData);\\n }\\n\\n /**\\n * @dev Internal function to implement lzReceive logic without needing to copy the basic parameter validation.\\n */\\n function _lzReceive(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address _executor,\\n bytes calldata _extraData\\n ) internal virtual;\\n}\\n\",\"keccak256\":\"0x0174e9f1ec4cefe4b5adc26c392269c699b9ff75965364e5b7264426a462c70b\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/OAppSender.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { SafeERC20, IERC20 } from \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\nimport { MessagingParams, MessagingFee, MessagingReceipt } from \\\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\\\";\\nimport { OAppCore } from \\\"./OAppCore.sol\\\";\\n\\n/**\\n * @title OAppSender\\n * @dev Abstract contract implementing the OAppSender functionality for sending messages to a LayerZero endpoint.\\n */\\nabstract contract OAppSender is OAppCore {\\n using SafeERC20 for IERC20;\\n\\n // Custom error messages\\n error NotEnoughNative(uint256 msgValue);\\n error LzTokenUnavailable();\\n\\n // @dev The version of the OAppSender implementation.\\n // @dev Version is bumped when changes are made to this contract.\\n uint64 internal constant SENDER_VERSION = 1;\\n\\n /**\\n * @notice Retrieves the OApp version information.\\n * @return senderVersion The version of the OAppSender.sol contract.\\n * @return receiverVersion The version of the OAppReceiver.sol contract.\\n *\\n * @dev Providing 0 as the default for OAppReceiver version. Indicates that the OAppReceiver is not implemented.\\n * ie. this is a SEND only OApp.\\n * @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions\\n */\\n function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) {\\n return (SENDER_VERSION, 0);\\n }\\n\\n /**\\n * @dev Internal function to interact with the LayerZero EndpointV2.quote() for fee calculation.\\n * @param _dstEid The destination endpoint ID.\\n * @param _message The message payload.\\n * @param _options Additional options for the message.\\n * @param _payInLzToken Flag indicating whether to pay the fee in LZ tokens.\\n * @return fee The calculated MessagingFee for the message.\\n * - nativeFee: The native fee for the message.\\n * - lzTokenFee: The LZ token fee for the message.\\n */\\n function _quote(\\n uint32 _dstEid,\\n bytes memory _message,\\n bytes memory _options,\\n bool _payInLzToken\\n ) internal view virtual returns (MessagingFee memory fee) {\\n return\\n endpoint.quote(\\n MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _payInLzToken),\\n address(this)\\n );\\n }\\n\\n /**\\n * @dev Internal function to interact with the LayerZero EndpointV2.send() for sending a message.\\n * @param _dstEid The destination endpoint ID.\\n * @param _message The message payload.\\n * @param _options Additional options for the message.\\n * @param _fee The calculated LayerZero fee for the message.\\n * - nativeFee: The native fee.\\n * - lzTokenFee: The lzToken fee.\\n * @param _refundAddress The address to receive any excess fee values sent to the endpoint.\\n * @return receipt The receipt for the sent message.\\n * - guid: The unique identifier for the sent message.\\n * - nonce: The nonce of the sent message.\\n * - fee: The LayerZero fee incurred for the message.\\n */\\n function _lzSend(\\n uint32 _dstEid,\\n bytes memory _message,\\n bytes memory _options,\\n MessagingFee memory _fee,\\n address _refundAddress\\n ) internal virtual returns (MessagingReceipt memory receipt) {\\n // @dev Push corresponding fees to the endpoint, any excess is sent back to the _refundAddress from the endpoint.\\n uint256 messageValue = _payNative(_fee.nativeFee);\\n if (_fee.lzTokenFee > 0) _payLzToken(_fee.lzTokenFee);\\n\\n return\\n // solhint-disable-next-line check-send-result\\n endpoint.send{ value: messageValue }(\\n MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _fee.lzTokenFee > 0),\\n _refundAddress\\n );\\n }\\n\\n /**\\n * @dev Internal function to pay the native fee associated with the message.\\n * @param _nativeFee The native fee to be paid.\\n * @return nativeFee The amount of native currency paid.\\n *\\n * @dev If the OApp needs to initiate MULTIPLE LayerZero messages in a single transaction,\\n * this will need to be overridden because msg.value would contain multiple lzFees.\\n * @dev Should be overridden in the event the LayerZero endpoint requires a different native currency.\\n * @dev Some EVMs use an ERC20 as a method for paying transactions/gasFees.\\n * @dev The endpoint is EITHER/OR, ie. it will NOT support both types of native payment at a time.\\n */\\n function _payNative(uint256 _nativeFee) internal virtual returns (uint256 nativeFee) {\\n if (msg.value != _nativeFee) revert NotEnoughNative(msg.value);\\n return _nativeFee;\\n }\\n\\n /**\\n * @dev Internal function to pay the LZ token fee associated with the message.\\n * @param _lzTokenFee The LZ token fee to be paid.\\n *\\n * @dev If the caller is trying to pay in the specified lzToken, then the lzTokenFee is passed to the endpoint.\\n * @dev Any excess sent, is passed back to the specified _refundAddress in the _lzSend().\\n */\\n function _payLzToken(uint256 _lzTokenFee) internal virtual {\\n // @dev Cannot cache the token because it is not immutable in the endpoint.\\n address lzToken = endpoint.lzToken();\\n if (lzToken == address(0)) revert LzTokenUnavailable();\\n\\n // Pay LZ token fee by sending tokens to the endpoint.\\n IERC20(lzToken).safeTransferFrom(msg.sender, address(endpoint), _lzTokenFee);\\n }\\n}\\n\",\"keccak256\":\"0x518cf4adca601923ed4baa6619846a253ea32b8d8775f8bc1faa3dfac7f67c20\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppCore.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { ILayerZeroEndpointV2 } from \\\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\\\";\\n\\n/**\\n * @title IOAppCore\\n */\\ninterface IOAppCore {\\n // Custom error messages\\n error OnlyPeer(uint32 eid, bytes32 sender);\\n error NoPeer(uint32 eid);\\n error InvalidEndpointCall();\\n error InvalidDelegate();\\n\\n // Event emitted when a peer (OApp) is set for a corresponding endpoint\\n event PeerSet(uint32 eid, bytes32 peer);\\n\\n /**\\n * @notice Retrieves the OApp version information.\\n * @return senderVersion The version of the OAppSender.sol contract.\\n * @return receiverVersion The version of the OAppReceiver.sol contract.\\n */\\n function oAppVersion() external view returns (uint64 senderVersion, uint64 receiverVersion);\\n\\n /**\\n * @notice Retrieves the LayerZero endpoint associated with the OApp.\\n * @return iEndpoint The LayerZero endpoint as an interface.\\n */\\n function endpoint() external view returns (ILayerZeroEndpointV2 iEndpoint);\\n\\n /**\\n * @notice Retrieves the peer (OApp) associated with a corresponding endpoint.\\n * @param _eid The endpoint ID.\\n * @return peer The peer address (OApp instance) associated with the corresponding endpoint.\\n */\\n function peers(uint32 _eid) external view returns (bytes32 peer);\\n\\n /**\\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\\n * @param _eid The endpoint ID.\\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\\n */\\n function setPeer(uint32 _eid, bytes32 _peer) external;\\n\\n /**\\n * @notice Sets the delegate address for the OApp Core.\\n * @param _delegate The address of the delegate to be set.\\n */\\n function setDelegate(address _delegate) external;\\n}\\n\",\"keccak256\":\"0x40e49f2de74506e1da5dcaed53a39853f691647f4ceb0fccc8f49a68d3f47c58\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppMsgInspector.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\n/**\\n * @title IOAppMsgInspector\\n * @dev Interface for the OApp Message Inspector, allowing examination of message and options contents.\\n */\\ninterface IOAppMsgInspector {\\n // Custom error message for inspection failure\\n error InspectionFailed(bytes message, bytes options);\\n\\n /**\\n * @notice Allows the inspector to examine LayerZero message contents and optionally throw a revert if invalid.\\n * @param _message The message payload to be inspected.\\n * @param _options Additional options or parameters for inspection.\\n * @return valid A boolean indicating whether the inspection passed (true) or failed (false).\\n *\\n * @dev Optionally done as a revert, OR use the boolean provided to handle the failure.\\n */\\n function inspect(bytes calldata _message, bytes calldata _options) external view returns (bool valid);\\n}\\n\",\"keccak256\":\"0x339654e699043c400cad92de209aa23855ce10211c31cf4114042cc5224d3b7c\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppOptionsType3.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\n/**\\n * @dev Struct representing enforced option parameters.\\n */\\nstruct EnforcedOptionParam {\\n uint32 eid; // Endpoint ID\\n uint16 msgType; // Message Type\\n bytes options; // Additional options\\n}\\n\\n/**\\n * @title IOAppOptionsType3\\n * @dev Interface for the OApp with Type 3 Options, allowing the setting and combining of enforced options.\\n */\\ninterface IOAppOptionsType3 {\\n // Custom error message for invalid options\\n error InvalidOptions(bytes options);\\n\\n // Event emitted when enforced options are set\\n event EnforcedOptionSet(EnforcedOptionParam[] _enforcedOptions);\\n\\n /**\\n * @notice Sets enforced options for specific endpoint and message type combinations.\\n * @param _enforcedOptions An array of EnforcedOptionParam structures specifying enforced options.\\n */\\n function setEnforcedOptions(EnforcedOptionParam[] calldata _enforcedOptions) external;\\n\\n /**\\n * @notice Combines options for a given endpoint and message type.\\n * @param _eid The endpoint ID.\\n * @param _msgType The OApp message type.\\n * @param _extraOptions Additional options passed by the caller.\\n * @return options The combination of caller specified options AND enforced options.\\n */\\n function combineOptions(\\n uint32 _eid,\\n uint16 _msgType,\\n bytes calldata _extraOptions\\n ) external view returns (bytes memory options);\\n}\\n\",\"keccak256\":\"0x9fc08a51e9d7c9c710c4eb26f84fe77228305ad7da63fa486ff24ebf2f3bc461\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppReceiver.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.20;\\n\\nimport { ILayerZeroReceiver, Origin } from \\\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroReceiver.sol\\\";\\n\\ninterface IOAppReceiver is ILayerZeroReceiver {\\n /**\\n * @notice Indicates whether an address is an approved composeMsg sender to the Endpoint.\\n * @param _origin The origin information containing the source endpoint and sender address.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address on the src chain.\\n * - nonce: The nonce of the message.\\n * @param _message The lzReceive payload.\\n * @param _sender The sender address.\\n * @return isSender Is a valid sender.\\n *\\n * @dev Applications can optionally choose to implement a separate composeMsg sender that is NOT the bridging layer.\\n * @dev The default sender IS the OAppReceiver implementer.\\n */\\n function isComposeMsgSender(\\n Origin calldata _origin,\\n bytes calldata _message,\\n address _sender\\n ) external view returns (bool isSender);\\n}\\n\",\"keccak256\":\"0xd26135185e19b3732746d4a9e2923e896f28dec8664bab161faea2ee26fcdc3d\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { Ownable } from \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\nimport { IOAppOptionsType3, EnforcedOptionParam } from \\\"../interfaces/IOAppOptionsType3.sol\\\";\\n\\n/**\\n * @title OAppOptionsType3\\n * @dev Abstract contract implementing the IOAppOptionsType3 interface with type 3 options.\\n */\\nabstract contract OAppOptionsType3 is IOAppOptionsType3, Ownable {\\n uint16 internal constant OPTION_TYPE_3 = 3;\\n\\n // @dev The \\\"msgType\\\" should be defined in the child contract.\\n mapping(uint32 eid => mapping(uint16 msgType => bytes enforcedOption)) public enforcedOptions;\\n\\n /**\\n * @dev Sets the enforced options for specific endpoint and message type combinations.\\n * @param _enforcedOptions An array of EnforcedOptionParam structures specifying enforced options.\\n *\\n * @dev Only the owner/admin of the OApp can call this function.\\n * @dev Provides a way for the OApp to enforce things like paying for PreCrime, AND/OR minimum dst lzReceive gas amounts etc.\\n * @dev These enforced options can vary as the potential options/execution on the remote may differ as per the msgType.\\n * eg. Amount of lzReceive() gas necessary to deliver a lzCompose() message adds overhead you dont want to pay\\n * if you are only making a standard LayerZero message ie. lzReceive() WITHOUT sendCompose().\\n */\\n function setEnforcedOptions(EnforcedOptionParam[] calldata _enforcedOptions) public virtual onlyOwner {\\n _setEnforcedOptions(_enforcedOptions);\\n }\\n\\n /**\\n * @dev Sets the enforced options for specific endpoint and message type combinations.\\n * @param _enforcedOptions An array of EnforcedOptionParam structures specifying enforced options.\\n *\\n * @dev Provides a way for the OApp to enforce things like paying for PreCrime, AND/OR minimum dst lzReceive gas amounts etc.\\n * @dev These enforced options can vary as the potential options/execution on the remote may differ as per the msgType.\\n * eg. Amount of lzReceive() gas necessary to deliver a lzCompose() message adds overhead you dont want to pay\\n * if you are only making a standard LayerZero message ie. lzReceive() WITHOUT sendCompose().\\n */\\n function _setEnforcedOptions(EnforcedOptionParam[] memory _enforcedOptions) internal virtual {\\n for (uint256 i = 0; i < _enforcedOptions.length; i++) {\\n // @dev Enforced options are only available for optionType 3, as type 1 and 2 dont support combining.\\n _assertOptionsType3(_enforcedOptions[i].options);\\n enforcedOptions[_enforcedOptions[i].eid][_enforcedOptions[i].msgType] = _enforcedOptions[i].options;\\n }\\n\\n emit EnforcedOptionSet(_enforcedOptions);\\n }\\n\\n /**\\n * @notice Combines options for a given endpoint and message type.\\n * @param _eid The endpoint ID.\\n * @param _msgType The OAPP message type.\\n * @param _extraOptions Additional options passed by the caller.\\n * @return options The combination of caller specified options AND enforced options.\\n *\\n * @dev If there is an enforced lzReceive option:\\n * - {gasLimit: 200k, msg.value: 1 ether} AND a caller supplies a lzReceive option: {gasLimit: 100k, msg.value: 0.5 ether}\\n * - The resulting options will be {gasLimit: 300k, msg.value: 1.5 ether} when the message is executed on the remote lzReceive() function.\\n * @dev This presence of duplicated options is handled off-chain in the verifier/executor.\\n */\\n function combineOptions(\\n uint32 _eid,\\n uint16 _msgType,\\n bytes calldata _extraOptions\\n ) public view virtual returns (bytes memory) {\\n bytes memory enforced = enforcedOptions[_eid][_msgType];\\n\\n // No enforced options, pass whatever the caller supplied, even if it's empty or legacy type 1/2 options.\\n if (enforced.length == 0) return _extraOptions;\\n\\n // No caller options, return enforced\\n if (_extraOptions.length == 0) return enforced;\\n\\n // @dev If caller provided _extraOptions, must be type 3 as its the ONLY type that can be combined.\\n if (_extraOptions.length >= 2) {\\n _assertOptionsType3(_extraOptions);\\n // @dev Remove the first 2 bytes containing the type from the _extraOptions and combine with enforced.\\n return bytes.concat(enforced, _extraOptions[2:]);\\n }\\n\\n // No valid set of options was found.\\n revert InvalidOptions(_extraOptions);\\n }\\n\\n /**\\n * @dev Internal function to assert that options are of type 3.\\n * @param _options The options to be checked.\\n */\\n function _assertOptionsType3(bytes memory _options) internal pure virtual {\\n uint16 optionsType;\\n assembly {\\n optionsType := mload(add(_options, 2))\\n }\\n if (optionsType != OPTION_TYPE_3) revert InvalidOptions(_options);\\n }\\n}\\n\",\"keccak256\":\"0x5275636cd47e660a2fdf6c7fe9d41ff3cc866b785cc8a9d88c1b8ca983509f01\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/precrime/OAppPreCrimeSimulator.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { Ownable } from \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\nimport { IPreCrime } from \\\"./interfaces/IPreCrime.sol\\\";\\nimport { IOAppPreCrimeSimulator, InboundPacket, Origin } from \\\"./interfaces/IOAppPreCrimeSimulator.sol\\\";\\n\\n/**\\n * @title OAppPreCrimeSimulator\\n * @dev Abstract contract serving as the base for preCrime simulation functionality in an OApp.\\n */\\nabstract contract OAppPreCrimeSimulator is IOAppPreCrimeSimulator, Ownable {\\n // The address of the preCrime implementation.\\n address public preCrime;\\n\\n /**\\n * @dev Retrieves the address of the OApp contract.\\n * @return The address of the OApp contract.\\n *\\n * @dev The simulator contract is the base contract for the OApp by default.\\n * @dev If the simulator is a separate contract, override this function.\\n */\\n function oApp() external view virtual returns (address) {\\n return address(this);\\n }\\n\\n /**\\n * @dev Sets the preCrime contract address.\\n * @param _preCrime The address of the preCrime contract.\\n */\\n function setPreCrime(address _preCrime) public virtual onlyOwner {\\n preCrime = _preCrime;\\n emit PreCrimeSet(_preCrime);\\n }\\n\\n /**\\n * @dev Interface for pre-crime simulations. Always reverts at the end with the simulation results.\\n * @param _packets An array of InboundPacket objects representing received packets to be delivered.\\n *\\n * @dev WARNING: MUST revert at the end with the simulation results.\\n * @dev Gives the preCrime implementation the ability to mock sending packets to the lzReceive function,\\n * WITHOUT actually executing them.\\n */\\n function lzReceiveAndRevert(InboundPacket[] calldata _packets) public payable virtual {\\n for (uint256 i = 0; i < _packets.length; i++) {\\n InboundPacket calldata packet = _packets[i];\\n\\n // Ignore packets that are not from trusted peers.\\n if (!isPeer(packet.origin.srcEid, packet.origin.sender)) continue;\\n\\n // @dev Because a verifier is calling this function, it doesnt have access to executor params:\\n // - address _executor\\n // - bytes calldata _extraData\\n // preCrime will NOT work for OApps that rely on these two parameters inside of their _lzReceive().\\n // They are instead stubbed to default values, address(0) and bytes(\\\"\\\")\\n // @dev Calling this.lzReceiveSimulate removes ability for assembly return 0 callstack exit,\\n // which would cause the revert to be ignored.\\n this.lzReceiveSimulate{ value: packet.value }(\\n packet.origin,\\n packet.guid,\\n packet.message,\\n packet.executor,\\n packet.extraData\\n );\\n }\\n\\n // @dev Revert with the simulation results. msg.sender must implement IPreCrime.buildSimulationResult().\\n revert SimulationResult(IPreCrime(msg.sender).buildSimulationResult());\\n }\\n\\n /**\\n * @dev Is effectively an internal function because msg.sender must be address(this).\\n * Allows resetting the call stack for 'internal' calls.\\n * @param _origin The origin information containing the source endpoint and sender address.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address on the src chain.\\n * - nonce: The nonce of the message.\\n * @param _guid The unique identifier of the packet.\\n * @param _message The message payload of the packet.\\n * @param _executor The executor address for the packet.\\n * @param _extraData Additional data for the packet.\\n */\\n function lzReceiveSimulate(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address _executor,\\n bytes calldata _extraData\\n ) external payable virtual {\\n // @dev Ensure ONLY can be called 'internally'.\\n if (msg.sender != address(this)) revert OnlySelf();\\n _lzReceiveSimulate(_origin, _guid, _message, _executor, _extraData);\\n }\\n\\n /**\\n * @dev Internal function to handle the OAppPreCrimeSimulator simulated receive.\\n * @param _origin The origin information.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address from the src chain.\\n * - nonce: The nonce of the LayerZero message.\\n * @param _guid The GUID of the LayerZero message.\\n * @param _message The LayerZero message.\\n * @param _executor The address of the off-chain executor.\\n * @param _extraData Arbitrary data passed by the msg executor.\\n *\\n * @dev Enables the preCrime simulator to mock sending lzReceive() messages,\\n * routes the msg down from the OAppPreCrimeSimulator, and back up to the OAppReceiver.\\n */\\n function _lzReceiveSimulate(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address _executor,\\n bytes calldata _extraData\\n ) internal virtual;\\n\\n /**\\n * @dev checks if the specified peer is considered 'trusted' by the OApp.\\n * @param _eid The endpoint Id to check.\\n * @param _peer The peer to check.\\n * @return Whether the peer passed is considered 'trusted' by the OApp.\\n */\\n function isPeer(uint32 _eid, bytes32 _peer) public view virtual returns (bool);\\n}\\n\",\"keccak256\":\"0x205a0abfd8b3c9af2740769f251381b84999b8e9347f3cd50de3ef8290a17750\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/precrime/interfaces/IOAppPreCrimeSimulator.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\n// @dev Import the Origin so it's exposed to OAppPreCrimeSimulator implementers.\\n// solhint-disable-next-line no-unused-import\\nimport { InboundPacket, Origin } from \\\"../libs/Packet.sol\\\";\\n\\n/**\\n * @title IOAppPreCrimeSimulator Interface\\n * @dev Interface for the preCrime simulation functionality in an OApp.\\n */\\ninterface IOAppPreCrimeSimulator {\\n // @dev simulation result used in PreCrime implementation\\n error SimulationResult(bytes result);\\n error OnlySelf();\\n\\n /**\\n * @dev Emitted when the preCrime contract address is set.\\n * @param preCrimeAddress The address of the preCrime contract.\\n */\\n event PreCrimeSet(address preCrimeAddress);\\n\\n /**\\n * @dev Retrieves the address of the preCrime contract implementation.\\n * @return The address of the preCrime contract.\\n */\\n function preCrime() external view returns (address);\\n\\n /**\\n * @dev Retrieves the address of the OApp contract.\\n * @return The address of the OApp contract.\\n */\\n function oApp() external view returns (address);\\n\\n /**\\n * @dev Sets the preCrime contract address.\\n * @param _preCrime The address of the preCrime contract.\\n */\\n function setPreCrime(address _preCrime) external;\\n\\n /**\\n * @dev Mocks receiving a packet, then reverts with a series of data to infer the state/result.\\n * @param _packets An array of LayerZero InboundPacket objects representing received packets.\\n */\\n function lzReceiveAndRevert(InboundPacket[] calldata _packets) external payable;\\n\\n /**\\n * @dev checks if the specified peer is considered 'trusted' by the OApp.\\n * @param _eid The endpoint Id to check.\\n * @param _peer The peer to check.\\n * @return Whether the peer passed is considered 'trusted' by the OApp.\\n */\\n function isPeer(uint32 _eid, bytes32 _peer) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x5d24db150949ea8e6437178e65a942e8c8b7f332e5daf32750f56b23b35b5bb2\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/precrime/interfaces/IPreCrime.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\nstruct PreCrimePeer {\\n uint32 eid;\\n bytes32 preCrime;\\n bytes32 oApp;\\n}\\n\\n// TODO not done yet\\ninterface IPreCrime {\\n error OnlyOffChain();\\n\\n // for simulate()\\n error PacketOversize(uint256 max, uint256 actual);\\n error PacketUnsorted();\\n error SimulationFailed(bytes reason);\\n\\n // for preCrime()\\n error SimulationResultNotFound(uint32 eid);\\n error InvalidSimulationResult(uint32 eid, bytes reason);\\n error CrimeFound(bytes crime);\\n\\n function getConfig(bytes[] calldata _packets, uint256[] calldata _packetMsgValues) external returns (bytes memory);\\n\\n function simulate(\\n bytes[] calldata _packets,\\n uint256[] calldata _packetMsgValues\\n ) external payable returns (bytes memory);\\n\\n function buildSimulationResult() external view returns (bytes memory);\\n\\n function preCrime(\\n bytes[] calldata _packets,\\n uint256[] calldata _packetMsgValues,\\n bytes[] calldata _simulations\\n ) external;\\n\\n function version() external view returns (uint64 major, uint8 minor);\\n}\\n\",\"keccak256\":\"0xc8d869f27ef8ceb2e13fdf6a70682fd4dee3f90c4924eb8e125bc1e66cb6af84\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/precrime/libs/Packet.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { Origin } from \\\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\\\";\\nimport { PacketV1Codec } from \\\"@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.sol\\\";\\n\\n/**\\n * @title InboundPacket\\n * @dev Structure representing an inbound packet received by the contract.\\n */\\nstruct InboundPacket {\\n Origin origin; // Origin information of the packet.\\n uint32 dstEid; // Destination endpointId of the packet.\\n address receiver; // Receiver address for the packet.\\n bytes32 guid; // Unique identifier of the packet.\\n uint256 value; // msg.value of the packet.\\n address executor; // Executor address for the packet.\\n bytes message; // Message payload of the packet.\\n bytes extraData; // Additional arbitrary data for the packet.\\n}\\n\\n/**\\n * @title PacketDecoder\\n * @dev Library for decoding LayerZero packets.\\n */\\nlibrary PacketDecoder {\\n using PacketV1Codec for bytes;\\n\\n /**\\n * @dev Decode an inbound packet from the given packet data.\\n * @param _packet The packet data to decode.\\n * @return packet An InboundPacket struct representing the decoded packet.\\n */\\n function decode(bytes calldata _packet) internal pure returns (InboundPacket memory packet) {\\n packet.origin = Origin(_packet.srcEid(), _packet.sender(), _packet.nonce());\\n packet.dstEid = _packet.dstEid();\\n packet.receiver = _packet.receiverB20();\\n packet.guid = _packet.guid();\\n packet.message = _packet.message();\\n }\\n\\n /**\\n * @dev Decode multiple inbound packets from the given packet data and associated message values.\\n * @param _packets An array of packet data to decode.\\n * @param _packetMsgValues An array of associated message values for each packet.\\n * @return packets An array of InboundPacket structs representing the decoded packets.\\n */\\n function decode(\\n bytes[] calldata _packets,\\n uint256[] memory _packetMsgValues\\n ) internal pure returns (InboundPacket[] memory packets) {\\n packets = new InboundPacket[](_packets.length);\\n for (uint256 i = 0; i < _packets.length; i++) {\\n bytes calldata packet = _packets[i];\\n packets[i] = PacketDecoder.decode(packet);\\n // @dev Allows the verifier to specify the msg.value that gets passed in lzReceive.\\n packets[i].value = _packetMsgValues[i];\\n }\\n }\\n}\\n\",\"keccak256\":\"0xcb2fb1c5b2eb3731de78b479b9c2ab3bba326fe0b0b3a008590f18e881e457a6\",\"license\":\"MIT\"},\"@layerzerolabs/oft-evm/contracts/OFT.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { ERC20 } from \\\"@openzeppelin/contracts/token/ERC20/ERC20.sol\\\";\\nimport { IOFT, OFTCore } from \\\"./OFTCore.sol\\\";\\n\\n/**\\n * @title OFT Contract\\n * @dev OFT is an ERC-20 token that extends the functionality of the OFTCore contract.\\n */\\nabstract contract OFT is OFTCore, ERC20 {\\n /**\\n * @dev Constructor for the OFT contract.\\n * @param _name The name of the OFT.\\n * @param _symbol The symbol of the OFT.\\n * @param _lzEndpoint The LayerZero endpoint address.\\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\\n */\\n constructor(\\n string memory _name,\\n string memory _symbol,\\n address _lzEndpoint,\\n address _delegate\\n ) ERC20(_name, _symbol) OFTCore(decimals(), _lzEndpoint, _delegate) {}\\n\\n /**\\n * @dev Retrieves the address of the underlying ERC20 implementation.\\n * @return The address of the OFT token.\\n *\\n * @dev In the case of OFT, address(this) and erc20 are the same contract.\\n */\\n function token() public view returns (address) {\\n return address(this);\\n }\\n\\n /**\\n * @notice Indicates whether the OFT contract requires approval of the 'token()' to send.\\n * @return requiresApproval Needs approval of the underlying token implementation.\\n *\\n * @dev In the case of OFT where the contract IS the token, approval is NOT required.\\n */\\n function approvalRequired() external pure virtual returns (bool) {\\n return false;\\n }\\n\\n /**\\n * @dev Burns tokens from the sender's specified balance.\\n * @param _from The address to debit the tokens from.\\n * @param _amountLD The amount of tokens to send in local decimals.\\n * @param _minAmountLD The minimum amount to send in local decimals.\\n * @param _dstEid The destination chain ID.\\n * @return amountSentLD The amount sent in local decimals.\\n * @return amountReceivedLD The amount received in local decimals on the remote.\\n */\\n function _debit(\\n address _from,\\n uint256 _amountLD,\\n uint256 _minAmountLD,\\n uint32 _dstEid\\n ) internal virtual override returns (uint256 amountSentLD, uint256 amountReceivedLD) {\\n (amountSentLD, amountReceivedLD) = _debitView(_amountLD, _minAmountLD, _dstEid);\\n\\n // @dev In NON-default OFT, amountSentLD could be 100, with a 10% fee, the amountReceivedLD amount is 90,\\n // therefore amountSentLD CAN differ from amountReceivedLD.\\n\\n // @dev Default OFT burns on src.\\n _burn(_from, amountSentLD);\\n }\\n\\n /**\\n * @dev Credits tokens to the specified address.\\n * @param _to The address to credit the tokens to.\\n * @param _amountLD The amount of tokens to credit in local decimals.\\n * @dev _srcEid The source chain ID.\\n * @return amountReceivedLD The amount of tokens ACTUALLY received in local decimals.\\n */\\n function _credit(\\n address _to,\\n uint256 _amountLD,\\n uint32 /*_srcEid*/\\n ) internal virtual override returns (uint256 amountReceivedLD) {\\n if (_to == address(0x0)) _to = address(0xdead); // _mint(...) does not support address(0x0)\\n // @dev Default OFT mints on dst.\\n _mint(_to, _amountLD);\\n // @dev In the case of NON-default OFT, the _amountLD MIGHT not be == amountReceivedLD.\\n return _amountLD;\\n }\\n}\\n\",\"keccak256\":\"0xdc3582e4a20e02a79050c17058a1f1f42a4335d1a70be06c0a52a3fb05d4c89a\",\"license\":\"MIT\"},\"@layerzerolabs/oft-evm/contracts/OFTCore.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { IERC20 } from \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\n\\nimport { OApp, Origin } from \\\"@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol\\\";\\nimport { OAppOptionsType3 } from \\\"@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol\\\";\\nimport { IOAppMsgInspector } from \\\"@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppMsgInspector.sol\\\";\\n\\nimport { OAppPreCrimeSimulator } from \\\"@layerzerolabs/oapp-evm/contracts/precrime/OAppPreCrimeSimulator.sol\\\";\\n\\nimport { IOFT, SendParam, OFTLimit, OFTReceipt, OFTFeeDetail, MessagingReceipt, MessagingFee } from \\\"./interfaces/IOFT.sol\\\";\\nimport { OFTMsgCodec } from \\\"./libs/OFTMsgCodec.sol\\\";\\nimport { OFTComposeMsgCodec } from \\\"./libs/OFTComposeMsgCodec.sol\\\";\\n\\n/**\\n * @title OFTCore\\n * @dev Abstract contract for the OftChain (OFT) token.\\n */\\nabstract contract OFTCore is IOFT, OApp, OAppPreCrimeSimulator, OAppOptionsType3 {\\n using OFTMsgCodec for bytes;\\n using OFTMsgCodec for bytes32;\\n\\n // @notice Provides a conversion rate when swapping between denominations of SD and LD\\n // - shareDecimals == SD == shared Decimals\\n // - localDecimals == LD == local decimals\\n // @dev Considers that tokens have different decimal amounts on various chains.\\n // @dev eg.\\n // For a token\\n // - locally with 4 decimals --> 1.2345 => uint(12345)\\n // - remotely with 2 decimals --> 1.23 => uint(123)\\n // - The conversion rate would be 10 ** (4 - 2) = 100\\n // @dev If you want to send 1.2345 -> (uint 12345), you CANNOT represent that value on the remote,\\n // you can only display 1.23 -> uint(123).\\n // @dev To preserve the dust that would otherwise be lost on that conversion,\\n // we need to unify a denomination that can be represented on ALL chains inside of the OFT mesh\\n uint256 public immutable decimalConversionRate;\\n\\n // @notice Msg types that are used to identify the various OFT operations.\\n // @dev This can be extended in child contracts for non-default oft operations\\n // @dev These values are used in things like combineOptions() in OAppOptionsType3.sol.\\n uint16 public constant SEND = 1;\\n uint16 public constant SEND_AND_CALL = 2;\\n\\n // Address of an optional contract to inspect both 'message' and 'options'\\n address public msgInspector;\\n event MsgInspectorSet(address inspector);\\n\\n /**\\n * @dev Constructor.\\n * @param _localDecimals The decimals of the token on the local chain (this chain).\\n * @param _endpoint The address of the LayerZero endpoint.\\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\\n */\\n constructor(uint8 _localDecimals, address _endpoint, address _delegate) OApp(_endpoint, _delegate) {\\n if (_localDecimals < sharedDecimals()) revert InvalidLocalDecimals();\\n decimalConversionRate = 10 ** (_localDecimals - sharedDecimals());\\n }\\n\\n /**\\n * @notice Retrieves interfaceID and the version of the OFT.\\n * @return interfaceId The interface ID.\\n * @return version The version.\\n *\\n * @dev interfaceId: This specific interface ID is '0x02e49c2c'.\\n * @dev version: Indicates a cross-chain compatible msg encoding with other OFTs.\\n * @dev If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented.\\n * ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)\\n */\\n function oftVersion() external pure virtual returns (bytes4 interfaceId, uint64 version) {\\n return (type(IOFT).interfaceId, 1);\\n }\\n\\n /**\\n * @dev Retrieves the shared decimals of the OFT.\\n * @return The shared decimals of the OFT.\\n *\\n * @dev Sets an implicit cap on the amount of tokens, over uint64.max() will need some sort of outbound cap / totalSupply cap\\n * Lowest common decimal denominator between chains.\\n * Defaults to 6 decimal places to provide up to 18,446,744,073,709.551615 units (max uint64).\\n * For tokens exceeding this totalSupply(), they will need to override the sharedDecimals function with something smaller.\\n * ie. 4 sharedDecimals would be 1,844,674,407,370,955.1615\\n */\\n function sharedDecimals() public view virtual returns (uint8) {\\n return 6;\\n }\\n\\n /**\\n * @dev Sets the message inspector address for the OFT.\\n * @param _msgInspector The address of the message inspector.\\n *\\n * @dev This is an optional contract that can be used to inspect both 'message' and 'options'.\\n * @dev Set it to address(0) to disable it, or set it to a contract address to enable it.\\n */\\n function setMsgInspector(address _msgInspector) public virtual onlyOwner {\\n msgInspector = _msgInspector;\\n emit MsgInspectorSet(_msgInspector);\\n }\\n\\n /**\\n * @notice Provides the fee breakdown and settings data for an OFT. Unused in the default implementation.\\n * @param _sendParam The parameters for the send operation.\\n * @return oftLimit The OFT limit information.\\n * @return oftFeeDetails The details of OFT fees.\\n * @return oftReceipt The OFT receipt information.\\n */\\n function quoteOFT(\\n SendParam calldata _sendParam\\n )\\n external\\n view\\n virtual\\n returns (OFTLimit memory oftLimit, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory oftReceipt)\\n {\\n uint256 minAmountLD = 0; // Unused in the default implementation.\\n uint256 maxAmountLD = IERC20(this.token()).totalSupply(); // Unused in the default implementation.\\n oftLimit = OFTLimit(minAmountLD, maxAmountLD);\\n\\n // Unused in the default implementation; reserved for future complex fee details.\\n oftFeeDetails = new OFTFeeDetail[](0);\\n\\n // @dev This is the same as the send() operation, but without the actual send.\\n // - amountSentLD is the amount in local decimals that would be sent from the sender.\\n // - amountReceivedLD is the amount in local decimals that will be credited to the recipient on the remote OFT instance.\\n // @dev The amountSentLD MIGHT not equal the amount the user actually receives. HOWEVER, the default does.\\n (uint256 amountSentLD, uint256 amountReceivedLD) = _debitView(\\n _sendParam.amountLD,\\n _sendParam.minAmountLD,\\n _sendParam.dstEid\\n );\\n oftReceipt = OFTReceipt(amountSentLD, amountReceivedLD);\\n }\\n\\n /**\\n * @notice Provides a quote for the send() operation.\\n * @param _sendParam The parameters for the send() operation.\\n * @param _payInLzToken Flag indicating whether the caller is paying in the LZ token.\\n * @return msgFee The calculated LayerZero messaging fee from the send() operation.\\n *\\n * @dev MessagingFee: LayerZero msg fee\\n * - nativeFee: The native fee.\\n * - lzTokenFee: The lzToken fee.\\n */\\n function quoteSend(\\n SendParam calldata _sendParam,\\n bool _payInLzToken\\n ) external view virtual returns (MessagingFee memory msgFee) {\\n // @dev mock the amount to receive, this is the same operation used in the send().\\n // The quote is as similar as possible to the actual send() operation.\\n (, uint256 amountReceivedLD) = _debitView(_sendParam.amountLD, _sendParam.minAmountLD, _sendParam.dstEid);\\n\\n // @dev Builds the options and OFT message to quote in the endpoint.\\n (bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam, amountReceivedLD);\\n\\n // @dev Calculates the LayerZero fee for the send() operation.\\n return _quote(_sendParam.dstEid, message, options, _payInLzToken);\\n }\\n\\n /**\\n * @dev Executes the send operation.\\n * @param _sendParam The parameters for the send operation.\\n * @param _fee The calculated fee for the send() operation.\\n * - nativeFee: The native fee.\\n * - lzTokenFee: The lzToken fee.\\n * @param _refundAddress The address to receive any excess funds.\\n * @return msgReceipt The receipt for the send operation.\\n * @return oftReceipt The OFT receipt information.\\n *\\n * @dev MessagingReceipt: LayerZero msg receipt\\n * - guid: The unique identifier for the sent message.\\n * - nonce: The nonce of the sent message.\\n * - fee: The LayerZero fee incurred for the message.\\n */\\n function send(\\n SendParam calldata _sendParam,\\n MessagingFee calldata _fee,\\n address _refundAddress\\n ) external payable virtual returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) {\\n return _send(_sendParam, _fee, _refundAddress);\\n }\\n\\n /**\\n * @dev Internal function to execute the send operation.\\n * @param _sendParam The parameters for the send operation.\\n * @param _fee The calculated fee for the send() operation.\\n * - nativeFee: The native fee.\\n * - lzTokenFee: The lzToken fee.\\n * @param _refundAddress The address to receive any excess funds.\\n * @return msgReceipt The receipt for the send operation.\\n * @return oftReceipt The OFT receipt information.\\n *\\n * @dev MessagingReceipt: LayerZero msg receipt\\n * - guid: The unique identifier for the sent message.\\n * - nonce: The nonce of the sent message.\\n * - fee: The LayerZero fee incurred for the message.\\n */\\n function _send(\\n SendParam calldata _sendParam,\\n MessagingFee calldata _fee,\\n address _refundAddress\\n ) internal virtual returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) {\\n // @dev Applies the token transfers regarding this send() operation.\\n // - amountSentLD is the amount in local decimals that was ACTUALLY sent/debited from the sender.\\n // - amountReceivedLD is the amount in local decimals that will be received/credited to the recipient on the remote OFT instance.\\n (uint256 amountSentLD, uint256 amountReceivedLD) = _debit(\\n msg.sender,\\n _sendParam.amountLD,\\n _sendParam.minAmountLD,\\n _sendParam.dstEid\\n );\\n\\n // @dev Builds the options and OFT message to quote in the endpoint.\\n (bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam, amountReceivedLD);\\n\\n // @dev Sends the message to the LayerZero endpoint and returns the LayerZero msg receipt.\\n msgReceipt = _lzSend(_sendParam.dstEid, message, options, _fee, _refundAddress);\\n // @dev Formulate the OFT receipt.\\n oftReceipt = OFTReceipt(amountSentLD, amountReceivedLD);\\n\\n emit OFTSent(msgReceipt.guid, _sendParam.dstEid, msg.sender, amountSentLD, amountReceivedLD);\\n }\\n\\n /**\\n * @dev Internal function to build the message and options.\\n * @param _sendParam The parameters for the send() operation.\\n * @param _amountLD The amount in local decimals.\\n * @return message The encoded message.\\n * @return options The encoded options.\\n */\\n function _buildMsgAndOptions(\\n SendParam calldata _sendParam,\\n uint256 _amountLD\\n ) internal view virtual returns (bytes memory message, bytes memory options) {\\n bool hasCompose;\\n // @dev This generated message has the msg.sender encoded into the payload so the remote knows who the caller is.\\n (message, hasCompose) = OFTMsgCodec.encode(\\n _sendParam.to,\\n _toSD(_amountLD),\\n // @dev Must be include a non empty bytes if you want to compose, EVEN if you dont need it on the remote.\\n // EVEN if you dont require an arbitrary payload to be sent... eg. '0x01'\\n _sendParam.composeMsg\\n );\\n // @dev Change the msg type depending if its composed or not.\\n uint16 msgType = hasCompose ? SEND_AND_CALL : SEND;\\n // @dev Combine the callers _extraOptions with the enforced options via the OAppOptionsType3.\\n options = combineOptions(_sendParam.dstEid, msgType, _sendParam.extraOptions);\\n\\n // @dev Optionally inspect the message and options depending if the OApp owner has set a msg inspector.\\n // @dev If it fails inspection, needs to revert in the implementation. ie. does not rely on return boolean\\n address inspector = msgInspector; // caches the msgInspector to avoid potential double storage read\\n if (inspector != address(0)) IOAppMsgInspector(inspector).inspect(message, options);\\n }\\n\\n /**\\n * @dev Internal function to handle the receive on the LayerZero endpoint.\\n * @param _origin The origin information.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address from the src chain.\\n * - nonce: The nonce of the LayerZero message.\\n * @param _guid The unique identifier for the received LayerZero message.\\n * @param _message The encoded message.\\n * @dev _executor The address of the executor.\\n * @dev _extraData Additional data.\\n */\\n function _lzReceive(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address /*_executor*/, // @dev unused in the default implementation.\\n bytes calldata /*_extraData*/ // @dev unused in the default implementation.\\n ) internal virtual override {\\n // @dev The src sending chain doesnt know the address length on this chain (potentially non-evm)\\n // Thus everything is bytes32() encoded in flight.\\n address toAddress = _message.sendTo().bytes32ToAddress();\\n // @dev Credit the amountLD to the recipient and return the ACTUAL amount the recipient received in local decimals\\n uint256 amountReceivedLD = _credit(toAddress, _toLD(_message.amountSD()), _origin.srcEid);\\n\\n if (_message.isComposed()) {\\n // @dev Proprietary composeMsg format for the OFT.\\n bytes memory composeMsg = OFTComposeMsgCodec.encode(\\n _origin.nonce,\\n _origin.srcEid,\\n amountReceivedLD,\\n _message.composeMsg()\\n );\\n\\n // @dev Stores the lzCompose payload that will be executed in a separate tx.\\n // Standardizes functionality for executing arbitrary contract invocation on some non-evm chains.\\n // @dev The off-chain executor will listen and process the msg based on the src-chain-callers compose options passed.\\n // @dev The index is used when a OApp needs to compose multiple msgs on lzReceive.\\n // For default OFT implementation there is only 1 compose msg per lzReceive, thus its always 0.\\n endpoint.sendCompose(toAddress, _guid, 0 /* the index of the composed message*/, composeMsg);\\n }\\n\\n emit OFTReceived(_guid, _origin.srcEid, toAddress, amountReceivedLD);\\n }\\n\\n /**\\n * @dev Internal function to handle the OAppPreCrimeSimulator simulated receive.\\n * @param _origin The origin information.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address from the src chain.\\n * - nonce: The nonce of the LayerZero message.\\n * @param _guid The unique identifier for the received LayerZero message.\\n * @param _message The LayerZero message.\\n * @param _executor The address of the off-chain executor.\\n * @param _extraData Arbitrary data passed by the msg executor.\\n *\\n * @dev Enables the preCrime simulator to mock sending lzReceive() messages,\\n * routes the msg down from the OAppPreCrimeSimulator, and back up to the OAppReceiver.\\n */\\n function _lzReceiveSimulate(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address _executor,\\n bytes calldata _extraData\\n ) internal virtual override {\\n _lzReceive(_origin, _guid, _message, _executor, _extraData);\\n }\\n\\n /**\\n * @dev Check if the peer is considered 'trusted' by the OApp.\\n * @param _eid The endpoint ID to check.\\n * @param _peer The peer to check.\\n * @return Whether the peer passed is considered 'trusted' by the OApp.\\n *\\n * @dev Enables OAppPreCrimeSimulator to check whether a potential Inbound Packet is from a trusted source.\\n */\\n function isPeer(uint32 _eid, bytes32 _peer) public view virtual override returns (bool) {\\n return peers[_eid] == _peer;\\n }\\n\\n /**\\n * @dev Internal function to remove dust from the given local decimal amount.\\n * @param _amountLD The amount in local decimals.\\n * @return amountLD The amount after removing dust.\\n *\\n * @dev Prevents the loss of dust when moving amounts between chains with different decimals.\\n * @dev eg. uint(123) with a conversion rate of 100 becomes uint(100).\\n */\\n function _removeDust(uint256 _amountLD) internal view virtual returns (uint256 amountLD) {\\n return (_amountLD / decimalConversionRate) * decimalConversionRate;\\n }\\n\\n /**\\n * @dev Internal function to convert an amount from shared decimals into local decimals.\\n * @param _amountSD The amount in shared decimals.\\n * @return amountLD The amount in local decimals.\\n */\\n function _toLD(uint64 _amountSD) internal view virtual returns (uint256 amountLD) {\\n return _amountSD * decimalConversionRate;\\n }\\n\\n /**\\n * @dev Internal function to convert an amount from local decimals into shared decimals.\\n * @param _amountLD The amount in local decimals.\\n * @return amountSD The amount in shared decimals.\\n *\\n * @dev Reverts if the _amountLD in shared decimals overflows uint64.\\n * @dev eg. uint(2**64 + 123) with a conversion rate of 1 wraps around 2**64 to uint(123).\\n */\\n function _toSD(uint256 _amountLD) internal view virtual returns (uint64 amountSD) {\\n uint256 _amountSD = _amountLD / decimalConversionRate;\\n if (_amountSD > type(uint64).max) revert AmountSDOverflowed(_amountSD);\\n return uint64(_amountSD);\\n }\\n\\n /**\\n * @dev Internal function to mock the amount mutation from a OFT debit() operation.\\n * @param _amountLD The amount to send in local decimals.\\n * @param _minAmountLD The minimum amount to send in local decimals.\\n * @dev _dstEid The destination endpoint ID.\\n * @return amountSentLD The amount sent, in local decimals.\\n * @return amountReceivedLD The amount to be received on the remote chain, in local decimals.\\n *\\n * @dev This is where things like fees would be calculated and deducted from the amount to be received on the remote.\\n */\\n function _debitView(\\n uint256 _amountLD,\\n uint256 _minAmountLD,\\n uint32 /*_dstEid*/\\n ) internal view virtual returns (uint256 amountSentLD, uint256 amountReceivedLD) {\\n // @dev Remove the dust so nothing is lost on the conversion between chains with different decimals for the token.\\n amountSentLD = _removeDust(_amountLD);\\n // @dev The amount to send is the same as amount received in the default implementation.\\n amountReceivedLD = amountSentLD;\\n\\n // @dev Check for slippage.\\n if (amountReceivedLD < _minAmountLD) {\\n revert SlippageExceeded(amountReceivedLD, _minAmountLD);\\n }\\n }\\n\\n /**\\n * @dev Internal function to perform a debit operation.\\n * @param _from The address to debit.\\n * @param _amountLD The amount to send in local decimals.\\n * @param _minAmountLD The minimum amount to send in local decimals.\\n * @param _dstEid The destination endpoint ID.\\n * @return amountSentLD The amount sent in local decimals.\\n * @return amountReceivedLD The amount received in local decimals on the remote.\\n *\\n * @dev Defined here but are intended to be overriden depending on the OFT implementation.\\n * @dev Depending on OFT implementation the _amountLD could differ from the amountReceivedLD.\\n */\\n function _debit(\\n address _from,\\n uint256 _amountLD,\\n uint256 _minAmountLD,\\n uint32 _dstEid\\n ) internal virtual returns (uint256 amountSentLD, uint256 amountReceivedLD);\\n\\n /**\\n * @dev Internal function to perform a credit operation.\\n * @param _to The address to credit.\\n * @param _amountLD The amount to credit in local decimals.\\n * @param _srcEid The source endpoint ID.\\n * @return amountReceivedLD The amount ACTUALLY received in local decimals.\\n *\\n * @dev Defined here but are intended to be overriden depending on the OFT implementation.\\n * @dev Depending on OFT implementation the _amountLD could differ from the amountReceivedLD.\\n */\\n function _credit(\\n address _to,\\n uint256 _amountLD,\\n uint32 _srcEid\\n ) internal virtual returns (uint256 amountReceivedLD);\\n}\\n\",\"keccak256\":\"0xdda89798c66928bba9e0fa44b3edf4710ff15cf46edadcf3e15c92d78fcc9ca8\",\"license\":\"MIT\"},\"@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { MessagingReceipt, MessagingFee } from \\\"@layerzerolabs/oapp-evm/contracts/oapp/OAppSender.sol\\\";\\n\\n/**\\n * @dev Struct representing token parameters for the OFT send() operation.\\n */\\nstruct SendParam {\\n uint32 dstEid; // Destination endpoint ID.\\n bytes32 to; // Recipient address.\\n uint256 amountLD; // Amount to send in local decimals.\\n uint256 minAmountLD; // Minimum amount to send in local decimals.\\n bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message.\\n bytes composeMsg; // The composed message for the send() operation.\\n bytes oftCmd; // The OFT command to be executed, unused in default OFT implementations.\\n}\\n\\n/**\\n * @dev Struct representing OFT limit information.\\n * @dev These amounts can change dynamically and are up the specific oft implementation.\\n */\\nstruct OFTLimit {\\n uint256 minAmountLD; // Minimum amount in local decimals that can be sent to the recipient.\\n uint256 maxAmountLD; // Maximum amount in local decimals that can be sent to the recipient.\\n}\\n\\n/**\\n * @dev Struct representing OFT receipt information.\\n */\\nstruct OFTReceipt {\\n uint256 amountSentLD; // Amount of tokens ACTUALLY debited from the sender in local decimals.\\n // @dev In non-default implementations, the amountReceivedLD COULD differ from this value.\\n uint256 amountReceivedLD; // Amount of tokens to be received on the remote side.\\n}\\n\\n/**\\n * @dev Struct representing OFT fee details.\\n * @dev Future proof mechanism to provide a standardized way to communicate fees to things like a UI.\\n */\\nstruct OFTFeeDetail {\\n int256 feeAmountLD; // Amount of the fee in local decimals.\\n string description; // Description of the fee.\\n}\\n\\n/**\\n * @title IOFT\\n * @dev Interface for the OftChain (OFT) token.\\n * @dev Does not inherit ERC20 to accommodate usage by OFTAdapter as well.\\n * @dev This specific interface ID is '0x02e49c2c'.\\n */\\ninterface IOFT {\\n // Custom error messages\\n error InvalidLocalDecimals();\\n error SlippageExceeded(uint256 amountLD, uint256 minAmountLD);\\n error AmountSDOverflowed(uint256 amountSD);\\n\\n // Events\\n event OFTSent(\\n bytes32 indexed guid, // GUID of the OFT message.\\n uint32 dstEid, // Destination Endpoint ID.\\n address indexed fromAddress, // Address of the sender on the src chain.\\n uint256 amountSentLD, // Amount of tokens sent in local decimals.\\n uint256 amountReceivedLD // Amount of tokens received in local decimals.\\n );\\n event OFTReceived(\\n bytes32 indexed guid, // GUID of the OFT message.\\n uint32 srcEid, // Source Endpoint ID.\\n address indexed toAddress, // Address of the recipient on the dst chain.\\n uint256 amountReceivedLD // Amount of tokens received in local decimals.\\n );\\n\\n /**\\n * @notice Retrieves interfaceID and the version of the OFT.\\n * @return interfaceId The interface ID.\\n * @return version The version.\\n *\\n * @dev interfaceId: This specific interface ID is '0x02e49c2c'.\\n * @dev version: Indicates a cross-chain compatible msg encoding with other OFTs.\\n * @dev If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented.\\n * ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)\\n */\\n function oftVersion() external view returns (bytes4 interfaceId, uint64 version);\\n\\n /**\\n * @notice Retrieves the address of the token associated with the OFT.\\n * @return token The address of the ERC20 token implementation.\\n */\\n function token() external view returns (address);\\n\\n /**\\n * @notice Indicates whether the OFT contract requires approval of the 'token()' to send.\\n * @return requiresApproval Needs approval of the underlying token implementation.\\n *\\n * @dev Allows things like wallet implementers to determine integration requirements,\\n * without understanding the underlying token implementation.\\n */\\n function approvalRequired() external view returns (bool);\\n\\n /**\\n * @notice Retrieves the shared decimals of the OFT.\\n * @return sharedDecimals The shared decimals of the OFT.\\n */\\n function sharedDecimals() external view returns (uint8);\\n\\n /**\\n * @notice Provides the fee breakdown and settings data for an OFT. Unused in the default implementation.\\n * @param _sendParam The parameters for the send operation.\\n * @return limit The OFT limit information.\\n * @return oftFeeDetails The details of OFT fees.\\n * @return receipt The OFT receipt information.\\n */\\n function quoteOFT(\\n SendParam calldata _sendParam\\n ) external view returns (OFTLimit memory, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory);\\n\\n /**\\n * @notice Provides a quote for the send() operation.\\n * @param _sendParam The parameters for the send() operation.\\n * @param _payInLzToken Flag indicating whether the caller is paying in the LZ token.\\n * @return fee The calculated LayerZero messaging fee from the send() operation.\\n *\\n * @dev MessagingFee: LayerZero msg fee\\n * - nativeFee: The native fee.\\n * - lzTokenFee: The lzToken fee.\\n */\\n function quoteSend(SendParam calldata _sendParam, bool _payInLzToken) external view returns (MessagingFee memory);\\n\\n /**\\n * @notice Executes the send() operation.\\n * @param _sendParam The parameters for the send operation.\\n * @param _fee The fee information supplied by the caller.\\n * - nativeFee: The native fee.\\n * - lzTokenFee: The lzToken fee.\\n * @param _refundAddress The address to receive any excess funds from fees etc. on the src.\\n * @return receipt The LayerZero messaging receipt from the send() operation.\\n * @return oftReceipt The OFT receipt information.\\n *\\n * @dev MessagingReceipt: LayerZero msg receipt\\n * - guid: The unique identifier for the sent message.\\n * - nonce: The nonce of the sent message.\\n * - fee: The LayerZero fee incurred for the message.\\n */\\n function send(\\n SendParam calldata _sendParam,\\n MessagingFee calldata _fee,\\n address _refundAddress\\n ) external payable returns (MessagingReceipt memory, OFTReceipt memory);\\n}\\n\",\"keccak256\":\"0xc60c7b4374b3d89f33b8de982f463c92374a8548800c816fe776f0ec76351fb0\",\"license\":\"MIT\"},\"@layerzerolabs/oft-evm/contracts/libs/OFTComposeMsgCodec.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nlibrary OFTComposeMsgCodec {\\n // Offset constants for decoding composed messages\\n uint8 private constant NONCE_OFFSET = 8;\\n uint8 private constant SRC_EID_OFFSET = 12;\\n uint8 private constant AMOUNT_LD_OFFSET = 44;\\n uint8 private constant COMPOSE_FROM_OFFSET = 76;\\n\\n /**\\n * @dev Encodes a OFT composed message.\\n * @param _nonce The nonce value.\\n * @param _srcEid The source endpoint ID.\\n * @param _amountLD The amount in local decimals.\\n * @param _composeMsg The composed message.\\n * @return _msg The encoded Composed message.\\n */\\n function encode(\\n uint64 _nonce,\\n uint32 _srcEid,\\n uint256 _amountLD,\\n bytes memory _composeMsg // 0x[composeFrom][composeMsg]\\n ) internal pure returns (bytes memory _msg) {\\n _msg = abi.encodePacked(_nonce, _srcEid, _amountLD, _composeMsg);\\n }\\n\\n /**\\n * @dev Retrieves the nonce for the composed message.\\n * @param _msg The message.\\n * @return The nonce value.\\n */\\n function nonce(bytes calldata _msg) internal pure returns (uint64) {\\n return uint64(bytes8(_msg[:NONCE_OFFSET]));\\n }\\n\\n /**\\n * @dev Retrieves the source endpoint ID for the composed message.\\n * @param _msg The message.\\n * @return The source endpoint ID.\\n */\\n function srcEid(bytes calldata _msg) internal pure returns (uint32) {\\n return uint32(bytes4(_msg[NONCE_OFFSET:SRC_EID_OFFSET]));\\n }\\n\\n /**\\n * @dev Retrieves the amount in local decimals from the composed message.\\n * @param _msg The message.\\n * @return The amount in local decimals.\\n */\\n function amountLD(bytes calldata _msg) internal pure returns (uint256) {\\n return uint256(bytes32(_msg[SRC_EID_OFFSET:AMOUNT_LD_OFFSET]));\\n }\\n\\n /**\\n * @dev Retrieves the composeFrom value from the composed message.\\n * @param _msg The message.\\n * @return The composeFrom value.\\n */\\n function composeFrom(bytes calldata _msg) internal pure returns (bytes32) {\\n return bytes32(_msg[AMOUNT_LD_OFFSET:COMPOSE_FROM_OFFSET]);\\n }\\n\\n /**\\n * @dev Retrieves the composed message.\\n * @param _msg The message.\\n * @return The composed message.\\n */\\n function composeMsg(bytes calldata _msg) internal pure returns (bytes memory) {\\n return _msg[COMPOSE_FROM_OFFSET:];\\n }\\n\\n /**\\n * @dev Converts an address to bytes32.\\n * @param _addr The address to convert.\\n * @return The bytes32 representation of the address.\\n */\\n function addressToBytes32(address _addr) internal pure returns (bytes32) {\\n return bytes32(uint256(uint160(_addr)));\\n }\\n\\n /**\\n * @dev Converts bytes32 to an address.\\n * @param _b The bytes32 value to convert.\\n * @return The address representation of bytes32.\\n */\\n function bytes32ToAddress(bytes32 _b) internal pure returns (address) {\\n return address(uint160(uint256(_b)));\\n }\\n}\\n\",\"keccak256\":\"0xaae73d6eb8b9561c43f1802f3c416c00ccd35f172b711f9781ccdf1b25a40db5\",\"license\":\"MIT\"},\"@layerzerolabs/oft-evm/contracts/libs/OFTMsgCodec.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nlibrary OFTMsgCodec {\\n // Offset constants for encoding and decoding OFT messages\\n uint8 private constant SEND_TO_OFFSET = 32;\\n uint8 private constant SEND_AMOUNT_SD_OFFSET = 40;\\n\\n /**\\n * @dev Encodes an OFT LayerZero message.\\n * @param _sendTo The recipient address.\\n * @param _amountShared The amount in shared decimals.\\n * @param _composeMsg The composed message.\\n * @return _msg The encoded message.\\n * @return hasCompose A boolean indicating whether the message has a composed payload.\\n */\\n function encode(\\n bytes32 _sendTo,\\n uint64 _amountShared,\\n bytes memory _composeMsg\\n ) internal view returns (bytes memory _msg, bool hasCompose) {\\n hasCompose = _composeMsg.length > 0;\\n // @dev Remote chains will want to know the composed function caller ie. msg.sender on the src.\\n _msg = hasCompose\\n ? abi.encodePacked(_sendTo, _amountShared, addressToBytes32(msg.sender), _composeMsg)\\n : abi.encodePacked(_sendTo, _amountShared);\\n }\\n\\n /**\\n * @dev Checks if the OFT message is composed.\\n * @param _msg The OFT message.\\n * @return A boolean indicating whether the message is composed.\\n */\\n function isComposed(bytes calldata _msg) internal pure returns (bool) {\\n return _msg.length > SEND_AMOUNT_SD_OFFSET;\\n }\\n\\n /**\\n * @dev Retrieves the recipient address from the OFT message.\\n * @param _msg The OFT message.\\n * @return The recipient address.\\n */\\n function sendTo(bytes calldata _msg) internal pure returns (bytes32) {\\n return bytes32(_msg[:SEND_TO_OFFSET]);\\n }\\n\\n /**\\n * @dev Retrieves the amount in shared decimals from the OFT message.\\n * @param _msg The OFT message.\\n * @return The amount in shared decimals.\\n */\\n function amountSD(bytes calldata _msg) internal pure returns (uint64) {\\n return uint64(bytes8(_msg[SEND_TO_OFFSET:SEND_AMOUNT_SD_OFFSET]));\\n }\\n\\n /**\\n * @dev Retrieves the composed message from the OFT message.\\n * @param _msg The OFT message.\\n * @return The composed message.\\n */\\n function composeMsg(bytes calldata _msg) internal pure returns (bytes memory) {\\n return _msg[SEND_AMOUNT_SD_OFFSET:];\\n }\\n\\n /**\\n * @dev Converts an address to bytes32.\\n * @param _addr The address to convert.\\n * @return The bytes32 representation of the address.\\n */\\n function addressToBytes32(address _addr) internal pure returns (bytes32) {\\n return bytes32(uint256(uint160(_addr)));\\n }\\n\\n /**\\n * @dev Converts bytes32 to an address.\\n * @param _b The bytes32 value to convert.\\n * @return The address representation of bytes32.\\n */\\n function bytes32ToAddress(bytes32 _b) internal pure returns (address) {\\n return address(uint160(uint256(_b)));\\n }\\n}\\n\",\"keccak256\":\"0x5358948017669c03e157f871d8c38e988f9004dbd0801ad3119d2487f0d40b0b\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/Ownable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\\n\\npragma solidity ^0.8.20;\\n\\nimport {Context} from \\\"../utils/Context.sol\\\";\\n\\n/**\\n * @dev Contract module which provides a basic access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * The initial owner is set to the address provided by the deployer. This can\\n * later be changed with {transferOwnership}.\\n *\\n * This module is used through inheritance. It will make available the modifier\\n * `onlyOwner`, which can be applied to your functions to restrict their use to\\n * the owner.\\n */\\nabstract contract Ownable is Context {\\n address private _owner;\\n\\n /**\\n * @dev The caller account is not authorized to perform an operation.\\n */\\n error OwnableUnauthorizedAccount(address account);\\n\\n /**\\n * @dev The owner is not a valid owner account. (eg. `address(0)`)\\n */\\n error OwnableInvalidOwner(address owner);\\n\\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\\n\\n /**\\n * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\\n */\\n constructor(address initialOwner) {\\n if (initialOwner == address(0)) {\\n revert OwnableInvalidOwner(address(0));\\n }\\n _transferOwnership(initialOwner);\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the owner.\\n */\\n modifier onlyOwner() {\\n _checkOwner();\\n _;\\n }\\n\\n /**\\n * @dev Returns the address of the current owner.\\n */\\n function owner() public view virtual returns (address) {\\n return _owner;\\n }\\n\\n /**\\n * @dev Throws if the sender is not the owner.\\n */\\n function _checkOwner() internal view virtual {\\n if (owner() != _msgSender()) {\\n revert OwnableUnauthorizedAccount(_msgSender());\\n }\\n }\\n\\n /**\\n * @dev Leaves the contract without owner. It will not be possible to call\\n * `onlyOwner` functions. Can only be called by the current owner.\\n *\\n * NOTE: Renouncing ownership will leave the contract without an owner,\\n * thereby disabling any functionality that is only available to the owner.\\n */\\n function renounceOwnership() public virtual onlyOwner {\\n _transferOwnership(address(0));\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual onlyOwner {\\n if (newOwner == address(0)) {\\n revert OwnableInvalidOwner(address(0));\\n }\\n _transferOwnership(newOwner);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Internal function without access restriction.\\n */\\n function _transferOwnership(address newOwner) internal virtual {\\n address oldOwner = _owner;\\n _owner = newOwner;\\n emit OwnershipTransferred(oldOwner, newOwner);\\n }\\n}\\n\",\"keccak256\":\"0xff6d0bb2e285473e5311d9d3caacb525ae3538a80758c10649a4d61029b017bb\",\"license\":\"MIT\"},\"@openzeppelin/contracts/interfaces/IERC1363.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1363.sol)\\n\\npragma solidity >=0.6.2;\\n\\nimport {IERC20} from \\\"./IERC20.sol\\\";\\nimport {IERC165} from \\\"./IERC165.sol\\\";\\n\\n/**\\n * @title IERC1363\\n * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].\\n *\\n * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract\\n * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.\\n */\\ninterface IERC1363 is IERC20, IERC165 {\\n /*\\n * Note: the ERC-165 identifier for this interface is 0xb0202a11.\\n * 0xb0202a11 ===\\n * bytes4(keccak256('transferAndCall(address,uint256)')) ^\\n * bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^\\n * bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^\\n * bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^\\n * bytes4(keccak256('approveAndCall(address,uint256)')) ^\\n * bytes4(keccak256('approveAndCall(address,uint256,bytes)'))\\n */\\n\\n /**\\n * @dev Moves a `value` amount of tokens from the caller's account to `to`\\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\\n * @param to The address which you want to transfer to.\\n * @param value The amount of tokens to be transferred.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function transferAndCall(address to, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from the caller's account to `to`\\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\\n * @param to The address which you want to transfer to.\\n * @param value The amount of tokens to be transferred.\\n * @param data Additional data with no specified format, sent in call to `to`.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism\\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\\n * @param from The address which you want to send tokens from.\\n * @param to The address which you want to transfer to.\\n * @param value The amount of tokens to be transferred.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function transferFromAndCall(address from, address to, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism\\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\\n * @param from The address which you want to send tokens from.\\n * @param to The address which you want to transfer to.\\n * @param value The amount of tokens to be transferred.\\n * @param data Additional data with no specified format, sent in call to `to`.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);\\n\\n /**\\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\\n * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.\\n * @param spender The address which will spend the funds.\\n * @param value The amount of tokens to be spent.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function approveAndCall(address spender, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\\n * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.\\n * @param spender The address which will spend the funds.\\n * @param value The amount of tokens to be spent.\\n * @param data Additional data with no specified format, sent in call to `spender`.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);\\n}\\n\",\"keccak256\":\"0xd5ea07362ab630a6a3dee4285a74cf2377044ca2e4be472755ad64d7c5d4b69d\",\"license\":\"MIT\"},\"@openzeppelin/contracts/interfaces/IERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC165.sol)\\n\\npragma solidity >=0.4.16;\\n\\nimport {IERC165} from \\\"../utils/introspection/IERC165.sol\\\";\\n\",\"keccak256\":\"0x0afcb7e740d1537b252cb2676f600465ce6938398569f09ba1b9ca240dde2dfc\",\"license\":\"MIT\"},\"@openzeppelin/contracts/interfaces/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC20.sol)\\n\\npragma solidity >=0.4.16;\\n\\nimport {IERC20} from \\\"../token/ERC20/IERC20.sol\\\";\\n\",\"keccak256\":\"0x1a6221315ce0307746c2c4827c125d821ee796c74a676787762f4778671d4f44\",\"license\":\"MIT\"},\"@openzeppelin/contracts/interfaces/draft-IERC6093.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.5.0) (interfaces/draft-IERC6093.sol)\\n\\npragma solidity >=0.8.4;\\n\\n/**\\n * @dev Standard ERC-20 Errors\\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-20 tokens.\\n */\\ninterface IERC20Errors {\\n /**\\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n * @param balance Current balance for the interacting account.\\n * @param needed Minimum amount required to perform a transfer.\\n */\\n error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);\\n\\n /**\\n * @dev Indicates a failure with the token `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n */\\n error ERC20InvalidSender(address sender);\\n\\n /**\\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\\n * @param receiver Address to which tokens are being transferred.\\n */\\n error ERC20InvalidReceiver(address receiver);\\n\\n /**\\n * @dev Indicates a failure with the `spender`\\u2019s `allowance`. Used in transfers.\\n * @param spender Address that may be allowed to operate on tokens without being their owner.\\n * @param allowance Amount of tokens a `spender` is allowed to operate with.\\n * @param needed Minimum amount required to perform a transfer.\\n */\\n error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);\\n\\n /**\\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\\n * @param approver Address initiating an approval operation.\\n */\\n error ERC20InvalidApprover(address approver);\\n\\n /**\\n * @dev Indicates a failure with the `spender` to be approved. Used in approvals.\\n * @param spender Address that may be allowed to operate on tokens without being their owner.\\n */\\n error ERC20InvalidSpender(address spender);\\n}\\n\\n/**\\n * @dev Standard ERC-721 Errors\\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-721 tokens.\\n */\\ninterface IERC721Errors {\\n /**\\n * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-721.\\n * Used in balance queries.\\n * @param owner Address of the current owner of a token.\\n */\\n error ERC721InvalidOwner(address owner);\\n\\n /**\\n * @dev Indicates a `tokenId` whose `owner` is the zero address.\\n * @param tokenId Identifier number of a token.\\n */\\n error ERC721NonexistentToken(uint256 tokenId);\\n\\n /**\\n * @dev Indicates an error related to the ownership over a particular token. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n * @param tokenId Identifier number of a token.\\n * @param owner Address of the current owner of a token.\\n */\\n error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);\\n\\n /**\\n * @dev Indicates a failure with the token `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n */\\n error ERC721InvalidSender(address sender);\\n\\n /**\\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\\n * @param receiver Address to which tokens are being transferred.\\n */\\n error ERC721InvalidReceiver(address receiver);\\n\\n /**\\n * @dev Indicates a failure with the `operator`\\u2019s approval. Used in transfers.\\n * @param operator Address that may be allowed to operate on tokens without being their owner.\\n * @param tokenId Identifier number of a token.\\n */\\n error ERC721InsufficientApproval(address operator, uint256 tokenId);\\n\\n /**\\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\\n * @param approver Address initiating an approval operation.\\n */\\n error ERC721InvalidApprover(address approver);\\n\\n /**\\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\\n * @param operator Address that may be allowed to operate on tokens without being their owner.\\n */\\n error ERC721InvalidOperator(address operator);\\n}\\n\\n/**\\n * @dev Standard ERC-1155 Errors\\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-1155 tokens.\\n */\\ninterface IERC1155Errors {\\n /**\\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n * @param balance Current balance for the interacting account.\\n * @param needed Minimum amount required to perform a transfer.\\n * @param tokenId Identifier number of a token.\\n */\\n error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);\\n\\n /**\\n * @dev Indicates a failure with the token `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n */\\n error ERC1155InvalidSender(address sender);\\n\\n /**\\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\\n * @param receiver Address to which tokens are being transferred.\\n */\\n error ERC1155InvalidReceiver(address receiver);\\n\\n /**\\n * @dev Indicates a failure with the `operator`\\u2019s approval. Used in transfers.\\n * @param operator Address that may be allowed to operate on tokens without being their owner.\\n * @param owner Address of the current owner of a token.\\n */\\n error ERC1155MissingApprovalForAll(address operator, address owner);\\n\\n /**\\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\\n * @param approver Address initiating an approval operation.\\n */\\n error ERC1155InvalidApprover(address approver);\\n\\n /**\\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\\n * @param operator Address that may be allowed to operate on tokens without being their owner.\\n */\\n error ERC1155InvalidOperator(address operator);\\n\\n /**\\n * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.\\n * Used in batch transfers.\\n * @param idsLength Length of the array of token identifiers\\n * @param valuesLength Length of the array of token amounts\\n */\\n error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);\\n}\\n\",\"keccak256\":\"0x1b88b3fb3d85ba5496d7d5f396f83ee1fddcdd6762059ff65992655b67920998\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/ERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.5.0) (token/ERC20/ERC20.sol)\\n\\npragma solidity ^0.8.20;\\n\\nimport {IERC20} from \\\"./IERC20.sol\\\";\\nimport {IERC20Metadata} from \\\"./extensions/IERC20Metadata.sol\\\";\\nimport {Context} from \\\"../../utils/Context.sol\\\";\\nimport {IERC20Errors} from \\\"../../interfaces/draft-IERC6093.sol\\\";\\n\\n/**\\n * @dev Implementation of the {IERC20} interface.\\n *\\n * This implementation is agnostic to the way tokens are created. This means\\n * that a supply mechanism has to be added in a derived contract using {_mint}.\\n *\\n * TIP: For a detailed writeup see our guide\\n * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How\\n * to implement supply mechanisms].\\n *\\n * The default value of {decimals} is 18. To change this, you should override\\n * this function so it returns a different value.\\n *\\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\\n * instead returning `false` on failure. This behavior is nonetheless\\n * conventional and does not conflict with the expectations of ERC-20\\n * applications.\\n */\\nabstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {\\n mapping(address account => uint256) private _balances;\\n\\n mapping(address account => mapping(address spender => uint256)) private _allowances;\\n\\n uint256 private _totalSupply;\\n\\n string private _name;\\n string private _symbol;\\n\\n /**\\n * @dev Sets the values for {name} and {symbol}.\\n *\\n * Both values are immutable: they can only be set once during construction.\\n */\\n constructor(string memory name_, string memory symbol_) {\\n _name = name_;\\n _symbol = symbol_;\\n }\\n\\n /**\\n * @dev Returns the name of the token.\\n */\\n function name() public view virtual returns (string memory) {\\n return _name;\\n }\\n\\n /**\\n * @dev Returns the symbol of the token, usually a shorter version of the\\n * name.\\n */\\n function symbol() public view virtual returns (string memory) {\\n return _symbol;\\n }\\n\\n /**\\n * @dev Returns the number of decimals used to get its user representation.\\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\\n *\\n * Tokens usually opt for a value of 18, imitating the relationship between\\n * Ether and Wei. This is the default value returned by this function, unless\\n * it's overridden.\\n *\\n * NOTE: This information is only used for _display_ purposes: it in\\n * no way affects any of the arithmetic of the contract, including\\n * {IERC20-balanceOf} and {IERC20-transfer}.\\n */\\n function decimals() public view virtual returns (uint8) {\\n return 18;\\n }\\n\\n /// @inheritdoc IERC20\\n function totalSupply() public view virtual returns (uint256) {\\n return _totalSupply;\\n }\\n\\n /// @inheritdoc IERC20\\n function balanceOf(address account) public view virtual returns (uint256) {\\n return _balances[account];\\n }\\n\\n /**\\n * @dev See {IERC20-transfer}.\\n *\\n * Requirements:\\n *\\n * - `to` cannot be the zero address.\\n * - the caller must have a balance of at least `value`.\\n */\\n function transfer(address to, uint256 value) public virtual returns (bool) {\\n address owner = _msgSender();\\n _transfer(owner, to, value);\\n return true;\\n }\\n\\n /// @inheritdoc IERC20\\n function allowance(address owner, address spender) public view virtual returns (uint256) {\\n return _allowances[owner][spender];\\n }\\n\\n /**\\n * @dev See {IERC20-approve}.\\n *\\n * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on\\n * `transferFrom`. This is semantically equivalent to an infinite approval.\\n *\\n * Requirements:\\n *\\n * - `spender` cannot be the zero address.\\n */\\n function approve(address spender, uint256 value) public virtual returns (bool) {\\n address owner = _msgSender();\\n _approve(owner, spender, value);\\n return true;\\n }\\n\\n /**\\n * @dev See {IERC20-transferFrom}.\\n *\\n * Skips emitting an {Approval} event indicating an allowance update. This is not\\n * required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].\\n *\\n * NOTE: Does not update the allowance if the current allowance\\n * is the maximum `uint256`.\\n *\\n * Requirements:\\n *\\n * - `from` and `to` cannot be the zero address.\\n * - `from` must have a balance of at least `value`.\\n * - the caller must have allowance for ``from``'s tokens of at least\\n * `value`.\\n */\\n function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {\\n address spender = _msgSender();\\n _spendAllowance(from, spender, value);\\n _transfer(from, to, value);\\n return true;\\n }\\n\\n /**\\n * @dev Moves a `value` amount of tokens from `from` to `to`.\\n *\\n * This internal function is equivalent to {transfer}, and can be used to\\n * e.g. implement automatic token fees, slashing mechanisms, etc.\\n *\\n * Emits a {Transfer} event.\\n *\\n * NOTE: This function is not virtual, {_update} should be overridden instead.\\n */\\n function _transfer(address from, address to, uint256 value) internal {\\n if (from == address(0)) {\\n revert ERC20InvalidSender(address(0));\\n }\\n if (to == address(0)) {\\n revert ERC20InvalidReceiver(address(0));\\n }\\n _update(from, to, value);\\n }\\n\\n /**\\n * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`\\n * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding\\n * this function.\\n *\\n * Emits a {Transfer} event.\\n */\\n function _update(address from, address to, uint256 value) internal virtual {\\n if (from == address(0)) {\\n // Overflow check required: The rest of the code assumes that totalSupply never overflows\\n _totalSupply += value;\\n } else {\\n uint256 fromBalance = _balances[from];\\n if (fromBalance < value) {\\n revert ERC20InsufficientBalance(from, fromBalance, value);\\n }\\n unchecked {\\n // Overflow not possible: value <= fromBalance <= totalSupply.\\n _balances[from] = fromBalance - value;\\n }\\n }\\n\\n if (to == address(0)) {\\n unchecked {\\n // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.\\n _totalSupply -= value;\\n }\\n } else {\\n unchecked {\\n // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.\\n _balances[to] += value;\\n }\\n }\\n\\n emit Transfer(from, to, value);\\n }\\n\\n /**\\n * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).\\n * Relies on the `_update` mechanism\\n *\\n * Emits a {Transfer} event with `from` set to the zero address.\\n *\\n * NOTE: This function is not virtual, {_update} should be overridden instead.\\n */\\n function _mint(address account, uint256 value) internal {\\n if (account == address(0)) {\\n revert ERC20InvalidReceiver(address(0));\\n }\\n _update(address(0), account, value);\\n }\\n\\n /**\\n * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.\\n * Relies on the `_update` mechanism.\\n *\\n * Emits a {Transfer} event with `to` set to the zero address.\\n *\\n * NOTE: This function is not virtual, {_update} should be overridden instead\\n */\\n function _burn(address account, uint256 value) internal {\\n if (account == address(0)) {\\n revert ERC20InvalidSender(address(0));\\n }\\n _update(account, address(0), value);\\n }\\n\\n /**\\n * @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.\\n *\\n * This internal function is equivalent to `approve`, and can be used to\\n * e.g. set automatic allowances for certain subsystems, etc.\\n *\\n * Emits an {Approval} event.\\n *\\n * Requirements:\\n *\\n * - `owner` cannot be the zero address.\\n * - `spender` cannot be the zero address.\\n *\\n * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.\\n */\\n function _approve(address owner, address spender, uint256 value) internal {\\n _approve(owner, spender, value, true);\\n }\\n\\n /**\\n * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.\\n *\\n * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by\\n * `_spendAllowance` during the `transferFrom` operation sets the flag to false. This saves gas by not emitting any\\n * `Approval` event during `transferFrom` operations.\\n *\\n * Anyone who wishes to continue emitting `Approval` events on the `transferFrom` operation can force the flag to\\n * true using the following override:\\n *\\n * ```solidity\\n * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {\\n * super._approve(owner, spender, value, true);\\n * }\\n * ```\\n *\\n * Requirements are the same as {_approve}.\\n */\\n function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {\\n if (owner == address(0)) {\\n revert ERC20InvalidApprover(address(0));\\n }\\n if (spender == address(0)) {\\n revert ERC20InvalidSpender(address(0));\\n }\\n _allowances[owner][spender] = value;\\n if (emitEvent) {\\n emit Approval(owner, spender, value);\\n }\\n }\\n\\n /**\\n * @dev Updates `owner`'s allowance for `spender` based on spent `value`.\\n *\\n * Does not update the allowance value in case of infinite allowance.\\n * Revert if not enough allowance is available.\\n *\\n * Does not emit an {Approval} event.\\n */\\n function _spendAllowance(address owner, address spender, uint256 value) internal virtual {\\n uint256 currentAllowance = allowance(owner, spender);\\n if (currentAllowance < type(uint256).max) {\\n if (currentAllowance < value) {\\n revert ERC20InsufficientAllowance(spender, currentAllowance, value);\\n }\\n unchecked {\\n _approve(owner, spender, currentAllowance - value, false);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x669464167428061ee0f8618b73b3ee90aff8405683e7ddde8cd77dadaa1afe29\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)\\n\\npragma solidity >=0.4.16;\\n\\n/**\\n * @dev Interface of the ERC-20 standard as defined in the ERC.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n\\n /**\\n * @dev Returns the value of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the value of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from the caller's account to `to`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address to, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\\n * caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from `from` to `to` using the\\n * allowance mechanism. `value` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(address from, address to, uint256 value) external returns (bool);\\n}\\n\",\"keccak256\":\"0x74ed01eb66b923d0d0cfe3be84604ac04b76482a55f9dd655e1ef4d367f95bc2\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/extensions/IERC20Metadata.sol)\\n\\npragma solidity >=0.6.2;\\n\\nimport {IERC20} from \\\"../IERC20.sol\\\";\\n\\n/**\\n * @dev Interface for the optional metadata functions from the ERC-20 standard.\\n */\\ninterface IERC20Metadata is IERC20 {\\n /**\\n * @dev Returns the name of the token.\\n */\\n function name() external view returns (string memory);\\n\\n /**\\n * @dev Returns the symbol of the token.\\n */\\n function symbol() external view returns (string memory);\\n\\n /**\\n * @dev Returns the decimals places of the token.\\n */\\n function decimals() external view returns (uint8);\\n}\\n\",\"keccak256\":\"0xd6fa4088198f04eef10c5bce8a2f4d60554b7ec4b987f684393c01bf79b94d9f\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.5.0) (token/ERC20/utils/SafeERC20.sol)\\n\\npragma solidity ^0.8.20;\\n\\nimport {IERC20} from \\\"../IERC20.sol\\\";\\nimport {IERC1363} from \\\"../../../interfaces/IERC1363.sol\\\";\\n\\n/**\\n * @title SafeERC20\\n * @dev Wrappers around ERC-20 operations that throw on failure (when the token\\n * contract returns false). Tokens that return no value (and instead revert or\\n * throw on failure) are also supported, non-reverting calls are assumed to be\\n * successful.\\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\\n */\\nlibrary SafeERC20 {\\n /**\\n * @dev An operation with an ERC-20 token failed.\\n */\\n error SafeERC20FailedOperation(address token);\\n\\n /**\\n * @dev Indicates a failed `decreaseAllowance` request.\\n */\\n error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);\\n\\n /**\\n * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful.\\n */\\n function safeTransfer(IERC20 token, address to, uint256 value) internal {\\n if (!_safeTransfer(token, to, value, true)) {\\n revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the\\n * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.\\n */\\n function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {\\n if (!_safeTransferFrom(token, from, to, value, true)) {\\n revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.\\n */\\n function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {\\n return _safeTransfer(token, to, value, false);\\n }\\n\\n /**\\n * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.\\n */\\n function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {\\n return _safeTransferFrom(token, from, to, value, false);\\n }\\n\\n /**\\n * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful.\\n *\\n * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the \\\"client\\\"\\n * smart contract uses ERC-7674 to set temporary allowances, then the \\\"client\\\" smart contract should avoid using\\n * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract\\n * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.\\n */\\n function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {\\n uint256 oldAllowance = token.allowance(address(this), spender);\\n forceApprove(token, spender, oldAllowance + value);\\n }\\n\\n /**\\n * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no\\n * value, non-reverting calls are assumed to be successful.\\n *\\n * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the \\\"client\\\"\\n * smart contract uses ERC-7674 to set temporary allowances, then the \\\"client\\\" smart contract should avoid using\\n * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract\\n * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.\\n */\\n function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {\\n unchecked {\\n uint256 currentAllowance = token.allowance(address(this), spender);\\n if (currentAllowance < requestedDecrease) {\\n revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);\\n }\\n forceApprove(token, spender, currentAllowance - requestedDecrease);\\n }\\n }\\n\\n /**\\n * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval\\n * to be set to zero before setting it to a non-zero value, such as USDT.\\n *\\n * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function\\n * only sets the \\\"standard\\\" allowance. Any temporary allowance will remain active, in addition to the value being\\n * set here.\\n */\\n function forceApprove(IERC20 token, address spender, uint256 value) internal {\\n if (!_safeApprove(token, spender, value, false)) {\\n if (!_safeApprove(token, spender, 0, true)) revert SafeERC20FailedOperation(address(token));\\n if (!_safeApprove(token, spender, value, true)) revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no\\n * code. This can be used to implement an {ERC721}-like safe transfer that relies on {ERC1363} checks when\\n * targeting contracts.\\n *\\n * Reverts if the returned value is other than `true`.\\n */\\n function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {\\n if (to.code.length == 0) {\\n safeTransfer(token, to, value);\\n } else if (!token.transferAndCall(to, value, data)) {\\n revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target\\n * has no code. This can be used to implement an {ERC721}-like safe transfer that relies on {ERC1363} checks when\\n * targeting contracts.\\n *\\n * Reverts if the returned value is other than `true`.\\n */\\n function transferFromAndCallRelaxed(\\n IERC1363 token,\\n address from,\\n address to,\\n uint256 value,\\n bytes memory data\\n ) internal {\\n if (to.code.length == 0) {\\n safeTransferFrom(token, from, to, value);\\n } else if (!token.transferFromAndCall(from, to, value, data)) {\\n revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no\\n * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when\\n * targeting contracts.\\n *\\n * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.\\n * Oppositely, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}\\n * once without retrying, and relies on the returned value to be true.\\n *\\n * Reverts if the returned value is other than `true`.\\n */\\n function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {\\n if (to.code.length == 0) {\\n forceApprove(token, to, value);\\n } else if (!token.approveAndCall(to, value, data)) {\\n revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Imitates a Solidity `token.transfer(to, value)` call, relaxing the requirement on the return value: the\\n * return value is optional (but if data is returned, it must not be false).\\n *\\n * @param token The token targeted by the call.\\n * @param to The recipient of the tokens\\n * @param value The amount of token to transfer\\n * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.\\n */\\n function _safeTransfer(IERC20 token, address to, uint256 value, bool bubble) private returns (bool success) {\\n bytes4 selector = IERC20.transfer.selector;\\n\\n assembly (\\\"memory-safe\\\") {\\n let fmp := mload(0x40)\\n mstore(0x00, selector)\\n mstore(0x04, and(to, shr(96, not(0))))\\n mstore(0x24, value)\\n success := call(gas(), token, 0, 0x00, 0x44, 0x00, 0x20)\\n // if call success and return is true, all is good.\\n // otherwise (not success or return is not true), we need to perform further checks\\n if iszero(and(success, eq(mload(0x00), 1))) {\\n // if the call was a failure and bubble is enabled, bubble the error\\n if and(iszero(success), bubble) {\\n returndatacopy(fmp, 0x00, returndatasize())\\n revert(fmp, returndatasize())\\n }\\n // if the return value is not true, then the call is only successful if:\\n // - the token address has code\\n // - the returndata is empty\\n success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))\\n }\\n mstore(0x40, fmp)\\n }\\n }\\n\\n /**\\n * @dev Imitates a Solidity `token.transferFrom(from, to, value)` call, relaxing the requirement on the return\\n * value: the return value is optional (but if data is returned, it must not be false).\\n *\\n * @param token The token targeted by the call.\\n * @param from The sender of the tokens\\n * @param to The recipient of the tokens\\n * @param value The amount of token to transfer\\n * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.\\n */\\n function _safeTransferFrom(\\n IERC20 token,\\n address from,\\n address to,\\n uint256 value,\\n bool bubble\\n ) private returns (bool success) {\\n bytes4 selector = IERC20.transferFrom.selector;\\n\\n assembly (\\\"memory-safe\\\") {\\n let fmp := mload(0x40)\\n mstore(0x00, selector)\\n mstore(0x04, and(from, shr(96, not(0))))\\n mstore(0x24, and(to, shr(96, not(0))))\\n mstore(0x44, value)\\n success := call(gas(), token, 0, 0x00, 0x64, 0x00, 0x20)\\n // if call success and return is true, all is good.\\n // otherwise (not success or return is not true), we need to perform further checks\\n if iszero(and(success, eq(mload(0x00), 1))) {\\n // if the call was a failure and bubble is enabled, bubble the error\\n if and(iszero(success), bubble) {\\n returndatacopy(fmp, 0x00, returndatasize())\\n revert(fmp, returndatasize())\\n }\\n // if the return value is not true, then the call is only successful if:\\n // - the token address has code\\n // - the returndata is empty\\n success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))\\n }\\n mstore(0x40, fmp)\\n mstore(0x60, 0)\\n }\\n }\\n\\n /**\\n * @dev Imitates a Solidity `token.approve(spender, value)` call, relaxing the requirement on the return value:\\n * the return value is optional (but if data is returned, it must not be false).\\n *\\n * @param token The token targeted by the call.\\n * @param spender The spender of the tokens\\n * @param value The amount of token to transfer\\n * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.\\n */\\n function _safeApprove(IERC20 token, address spender, uint256 value, bool bubble) private returns (bool success) {\\n bytes4 selector = IERC20.approve.selector;\\n\\n assembly (\\\"memory-safe\\\") {\\n let fmp := mload(0x40)\\n mstore(0x00, selector)\\n mstore(0x04, and(spender, shr(96, not(0))))\\n mstore(0x24, value)\\n success := call(gas(), token, 0, 0x00, 0x44, 0x00, 0x20)\\n // if call success and return is true, all is good.\\n // otherwise (not success or return is not true), we need to perform further checks\\n if iszero(and(success, eq(mload(0x00), 1))) {\\n // if the call was a failure and bubble is enabled, bubble the error\\n if and(iszero(success), bubble) {\\n returndatacopy(fmp, 0x00, returndatasize())\\n revert(fmp, returndatasize())\\n }\\n // if the return value is not true, then the call is only successful if:\\n // - the token address has code\\n // - the returndata is empty\\n success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))\\n }\\n mstore(0x40, fmp)\\n }\\n }\\n}\\n\",\"keccak256\":\"0x304d732678032a9781ae85c8f204c8fba3d3a5e31c02616964e75cfdc5049098\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Context.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\\n\\npragma solidity ^0.8.20;\\n\\n/**\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract Context {\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n\\n function _contextSuffixLength() internal view virtual returns (uint256) {\\n return 0;\\n }\\n}\\n\",\"keccak256\":\"0x493033a8d1b176a037b2cc6a04dad01a5c157722049bbecf632ca876224dd4b2\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/introspection/IERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)\\n\\npragma solidity >=0.4.16;\\n\\n/**\\n * @dev Interface of the ERC-165 standard, as defined in the\\n * https://eips.ethereum.org/EIPS/eip-165[ERC].\\n *\\n * Implementers can declare support of contract interfaces, which can then be\\n * queried by others ({ERC165Checker}).\\n *\\n * For an implementation, see {ERC165}.\\n */\\ninterface IERC165 {\\n /**\\n * @dev Returns true if this contract implements the interface defined by\\n * `interfaceId`. See the corresponding\\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]\\n * to learn more about how these ids are created.\\n *\\n * This function call must use less than 30 000 gas.\\n */\\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x8891738ffe910f0cf2da09566928589bf5d63f4524dd734fd9cedbac3274dd5c\",\"license\":\"MIT\"},\"contracts/MyOFT.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\r\\npragma solidity ^0.8.22;\\r\\n\\r\\nimport { Ownable } from \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\r\\nimport { OFT } from \\\"@layerzerolabs/oft-evm/contracts/OFT.sol\\\";\\r\\n\\r\\ncontract MyOFT is OFT {\\r\\n constructor(\\r\\n string memory _name,\\r\\n string memory _symbol,\\r\\n address _lzEndpoint,\\r\\n address _delegate\\r\\n ) OFT(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) {}\\r\\n\\r\\n /// @notice Open mint, TESTNET ONLY \\u2014 same as ToyOFT, so the demo tasks work against either.\\r\\n /// @dev Deliberately unguarded: the demo mints to itself before each send, and a testnet OFT\\r\\n /// with no way to obtain tokens cannot be used to exercise the DVN at all. Do NOT ship\\r\\n /// this to a network where the token has value.\\r\\n /// `virtual` because MyOFTMock declares the same function for the hardhat tests.\\r\\n function mint(address _to, uint256 _amount) public virtual {\\r\\n _mint(_to, _amount);\\r\\n }\\r\\n}\\r\\n\",\"keccak256\":\"0x742ac02bacb2a1fa397bf560593b446ea96b15f7031123129b0fa6bcbd4c0e80\",\"license\":\"UNLICENSED\"}},\"version\":1}", + "bytecode": "0x60c06040523480156200001157600080fd5b5060405162003790380380620037908339810160408190526200003491620002d2565b83838383838360128484818181818d6001600160a01b0381166200007257604051631e4fbdf760e01b81526000600482015260240160405180910390fd5b6200007d8162000198565b506001600160a01b038083166080528116620000ac57604051632d618d8160e21b815260040160405180910390fd5b60805160405163ca5eb5e160e01b81526001600160a01b0383811660048301529091169063ca5eb5e190602401600060405180830381600087803b158015620000f457600080fd5b505af115801562000109573d6000803e3d6000fd5b505050505050505062000121620001e860201b60201c565b60ff168360ff16101562000148576040516301e9714b60e41b815260040160405180910390fd5b6200015560068462000377565b6200016290600a62000496565b60a052506008915062000178905083826200053f565b5060096200018782826200053f565b50505050505050505050506200060b565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b600690565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126200021557600080fd5b81516001600160401b0380821115620002325762000232620001ed565b604051601f8301601f19908116603f011681019082821181831017156200025d576200025d620001ed565b81604052838152602092508660208588010111156200027b57600080fd5b600091505b838210156200029f578582018301518183018401529082019062000280565b6000602085830101528094505050505092915050565b80516001600160a01b0381168114620002cd57600080fd5b919050565b60008060008060808587031215620002e957600080fd5b84516001600160401b03808211156200030157600080fd5b6200030f8883890162000203565b955060208701519150808211156200032657600080fd5b50620003358782880162000203565b9350506200034660408601620002b5565b91506200035660608601620002b5565b905092959194509250565b634e487b7160e01b600052601160045260246000fd5b60ff828116828216039081111562000393576200039362000361565b92915050565b600181815b80851115620003da578160001904821115620003be57620003be62000361565b80851615620003cc57918102915b93841c93908002906200039e565b509250929050565b600082620003f35750600162000393565b81620004025750600062000393565b81600181146200041b5760028114620004265762000446565b600191505062000393565b60ff8411156200043a576200043a62000361565b50506001821b62000393565b5060208310610133831016604e8410600b84101617156200046b575081810a62000393565b62000477838362000399565b80600019048211156200048e576200048e62000361565b029392505050565b6000620004a760ff841683620003e2565b9392505050565b600181811c90821680620004c357607f821691505b602082108103620004e457634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200053a576000816000526020600020601f850160051c81016020861015620005155750805b601f850160051c820191505b81811015620005365782815560010162000521565b5050505b505050565b81516001600160401b038111156200055b576200055b620001ed565b62000573816200056c8454620004ae565b84620004ea565b602080601f831160018114620005ab5760008415620005925750858301515b600019600386901b1c1916600185901b17855562000536565b600085815260208120601f198616915b82811015620005dc57888601518255948401946001909101908401620005bb565b5085821015620005fb5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60805160a051613119620006776000396000818161064901528181611b0c01528181611b810152611d8601526000818161050801528181610a78015281816110d201528181611349015281816116b401528181611eab01528181611fe5015261209e01526131196000f3fe60806040526004361061025c5760003560e01c8063715018a611610144578063bb0b6a53116100b6578063d045a0dc1161007a578063d045a0dc14610780578063d424388514610793578063dd62ed3e146107b3578063f2fde38b146107f9578063fc0c546a1461048c578063ff7bd03d1461081957600080fd5b8063bb0b6a53146106df578063bc70b3541461070c578063bd815db01461072c578063c7c7f5b31461073f578063ca5eb5e11461076057600080fd5b806395d89b411161010857806395d89b4114610622578063963efcaa146106375780639f68b9641461066b578063a9059cbb1461067f578063b731ea0a1461069f578063b98bd070146106bf57600080fd5b8063715018a6146105805780637d25a05e1461059557806382413eac146105d0578063857749b0146105f05780638da5cb5b1461060457600080fd5b806323b872dd116101dd57806352ae2879116101a157806352ae28791461048c5780635535d4611461049f5780635a0dfe4d146104bf5780635e280f11146104f65780636fc1b31e1461052a57806370a082311461054a57600080fd5b806323b872dd146103dd578063313ce567146103fd5780633400288b1461041f5780633b6f743b1461043f57806340c10f191461046c57600080fd5b8063134d4f2511610224578063134d4f2514610338578063156a0d0f1461036057806317442b701461038757806318160ddd146103a95780631f5e1334146103c857600080fd5b806306fdde0314610261578063095ea7b31461028c5780630d35b415146102bc578063111ecdad146102eb57806313137d6514610323575b600080fd5b34801561026d57600080fd5b50610276610839565b60405161028391906121fd565b60405180910390f35b34801561029857600080fd5b506102ac6102a7366004612225565b6108cb565b6040519015158152602001610283565b3480156102c857600080fd5b506102dc6102d7366004612269565b6108e5565b6040516102839392919061229d565b3480156102f757600080fd5b5060045461030b906001600160a01b031681565b6040516001600160a01b039091168152602001610283565b610336610331366004612390565b610a76565b005b34801561034457600080fd5b5061034d600281565b60405161ffff9091168152602001610283565b34801561036c57600080fd5b506040805162b9270b60e21b81526001602082015201610283565b34801561039357600080fd5b5060408051600181526002602082015201610283565b3480156103b557600080fd5b506007545b604051908152602001610283565b3480156103d457600080fd5b5061034d600181565b3480156103e957600080fd5b506102ac6103f836600461242f565b610b36565b34801561040957600080fd5b5060125b60405160ff9091168152602001610283565b34801561042b57600080fd5b5061033661043a366004612489565b610b5c565b34801561044b57600080fd5b5061045f61045a3660046124b3565b610b72565b6040516102839190612504565b34801561047857600080fd5b50610336610487366004612225565b610bd9565b34801561049857600080fd5b503061030b565b3480156104ab57600080fd5b506102766104ba36600461252d565b610be3565b3480156104cb57600080fd5b506102ac6104da366004612489565b63ffffffff919091166000908152600160205260409020541490565b34801561050257600080fd5b5061030b7f000000000000000000000000000000000000000000000000000000000000000081565b34801561053657600080fd5b50610336610545366004612560565b610c88565b34801561055657600080fd5b506103ba610565366004612560565b6001600160a01b031660009081526005602052604090205490565b34801561058c57600080fd5b50610336610ce5565b3480156105a157600080fd5b506105b86105b0366004612489565b600092915050565b6040516001600160401b039091168152602001610283565b3480156105dc57600080fd5b506102ac6105eb36600461257d565b610cf9565b3480156105fc57600080fd5b50600661040d565b34801561061057600080fd5b506000546001600160a01b031661030b565b34801561062e57600080fd5b50610276610d0e565b34801561064357600080fd5b506103ba7f000000000000000000000000000000000000000000000000000000000000000081565b34801561067757600080fd5b5060006102ac565b34801561068b57600080fd5b506102ac61069a366004612225565b610d1d565b3480156106ab57600080fd5b5060025461030b906001600160a01b031681565b3480156106cb57600080fd5b506103366106da366004612627565b610d2b565b3480156106eb57600080fd5b506103ba6106fa366004612668565b60016020526000908152604090205481565b34801561071857600080fd5b50610276610727366004612683565b610d45565b61033661073a366004612627565b610eed565b61075261074d3660046126e3565b611077565b604051610283929190612750565b34801561076c57600080fd5b5061033661077b366004612560565b6110ab565b61033661078e366004612390565b611131565b34801561079f57600080fd5b506103366107ae366004612560565b611160565b3480156107bf57600080fd5b506103ba6107ce3660046127a2565b6001600160a01b03918216600090815260066020908152604080832093909416825291909152205490565b34801561080557600080fd5b50610336610814366004612560565b6111b6565b34801561082557600080fd5b506102ac6108343660046127d0565b6111f4565b606060088054610848906127ec565b80601f0160208091040260200160405190810160405280929190818152602001828054610874906127ec565b80156108c15780601f10610896576101008083540402835291602001916108c1565b820191906000526020600020905b8154815290600101906020018083116108a457829003601f168201915b5050505050905090565b6000336108d981858561122a565b60019150505b92915050565b60408051808201909152600080825260208201526060610918604051806040016040528060008152602001600081525090565b600080306001600160a01b031663fc0c546a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610959573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061097d9190612820565b6001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109ba573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109de919061283d565b60408051808201825284815260208082018490528251600080825291810190935290975091925090610a33565b604080518082019091526000815260606020820152815260200190600190039081610a0b5790505b509350600080610a58604089013560608a0135610a5360208c018c612668565b61123c565b60408051808201909152918252602082015296989597505050505050565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163314610ac6576040516391ac5e4f60e01b81523360048201526024015b60405180910390fd5b60208701803590610ae090610adb908a612668565b611278565b14610b1e57610af26020880188612668565b60405163309afaf360e21b815263ffffffff909116600482015260208801356024820152604401610abd565b610b2d878787878787876112b4565b50505050505050565b600033610b4485828561141b565b610b4f85858561149a565b60019150505b9392505050565b610b646114f9565b610b6e8282611526565b5050565b60408051808201909152600080825260208201526000610ba260408501356060860135610a536020880188612668565b915050600080610bb2868461157b565b9092509050610bcf610bc76020880188612668565b83838861169e565b9695505050505050565b610b6e828261177f565b600360209081526000928352604080842090915290825290208054610c07906127ec565b80601f0160208091040260200160405190810160405280929190818152602001828054610c33906127ec565b8015610c805780601f10610c5557610100808354040283529160200191610c80565b820191906000526020600020905b815481529060010190602001808311610c6357829003601f168201915b505050505081565b610c906114f9565b600480546001600160a01b0319166001600160a01b0383169081179091556040519081527ff0be4f1e87349231d80c36b33f9e8639658eeaf474014dee15a3e6a4d4414197906020015b60405180910390a150565b610ced6114f9565b610cf760006117b5565b565b6001600160a01b03811630145b949350505050565b606060098054610848906127ec565b6000336108d981858561149a565b610d336114f9565b610b6e610d40828461290d565b611805565b63ffffffff8416600090815260036020908152604080832061ffff87168452909152812080546060929190610d79906127ec565b80601f0160208091040260200160405190810160405280929190818152602001828054610da5906127ec565b8015610df25780601f10610dc757610100808354040283529160200191610df2565b820191906000526020600020905b815481529060010190602001808311610dd557829003601f168201915b505050505090508051600003610e425783838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929450610d069350505050565b6000839003610e52579050610d06565b60028310610ed057610e9984848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061190c92505050565b80610ea78460028188612a22565b604051602001610eb993929190612a4c565b604051602081830303815290604052915050610d06565b8383604051639a6d49cd60e01b8152600401610abd929190612a9d565b60005b81811015610ff65736838383818110610f0b57610f0b612ab1565b9050602002810190610f1d9190612ac7565b9050610f50610f2f6020830183612668565b602083013563ffffffff919091166000908152600160205260409020541490565b610f5a5750610fee565b3063d045a0dc60c08301358360a0810135610f79610100830183612ae8565b610f8a610100890160e08a01612560565b610f986101208a018a612ae8565b6040518963ffffffff1660e01b8152600401610fba9796959493929190612b43565b6000604051808303818588803b158015610fd357600080fd5b505af1158015610fe7573d6000803e3d6000fd5b5050505050505b600101610ef0565b50336001600160a01b0316638e9e70996040518163ffffffff1660e01b8152600401600060405180830381865afa158015611035573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261105d9190810190612bc9565b604051638351eea760e01b8152600401610abd91906121fd565b61107f612166565b604080518082019091526000808252602082015261109e858585611938565b915091505b935093915050565b6110b36114f9565b60405163ca5eb5e160e01b81526001600160a01b0382811660048301527f0000000000000000000000000000000000000000000000000000000000000000169063ca5eb5e190602401600060405180830381600087803b15801561111657600080fd5b505af115801561112a573d6000803e3d6000fd5b5050505050565b3330146111515760405163029a949d60e31b815260040160405180910390fd5b610b2d87878787878787610b1e565b6111686114f9565b600280546001600160a01b0319166001600160a01b0383169081179091556040519081527fd48d879cef83a1c0bdda516f27b13ddb1b3f8bbac1c9e1511bb2a659c242776090602001610cda565b6111be6114f9565b6001600160a01b0381166111e857604051631e4fbdf760e01b815260006004820152602401610abd565b6111f1816117b5565b50565b600060208201803590600190839061120c9086612668565b63ffffffff1681526020810191909152604001600020541492915050565b6112378383836001611a33565b505050565b60008061124885611b08565b9150819050838110156110a3576040516371c4efed60e01b81526004810182905260248101859052604401610abd565b63ffffffff8116600090815260016020526040812054806108df5760405163f6ff4fb760e01b815263ffffffff84166004820152602401610abd565b60006112c66112c38787611b3f565b90565b905060006112f2826112e06112db8a8a611b57565b611b7a565b6112ed60208d018d612668565b611baf565b905060288611156113b957600061132f61131260608c0160408d01612c36565b61131f60208d018d612668565b8461132a8c8c611bd7565b611c22565b604051633e5ac80960e11b81529091506001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690637cb59012906113859086908d906000908790600401612c53565b600060405180830381600087803b15801561139f57600080fd5b505af11580156113b3573d6000803e3d6000fd5b50505050505b6001600160a01b038216887fefed6d3500546b29533b128a29e3a94d70788727f0507505ac12eaf2e578fd9c6113f260208d018d612668565b6040805163ffffffff9092168252602082018690520160405180910390a3505050505050505050565b6001600160a01b03838116600090815260066020908152604080832093861683529290522054600019811015611494578181101561148557604051637dc7a0d960e11b81526001600160a01b03841660048201526024810182905260448101839052606401610abd565b61149484848484036000611a33565b50505050565b6001600160a01b0383166114c457604051634b637e8f60e11b815260006004820152602401610abd565b6001600160a01b0382166114ee5760405163ec442f0560e01b815260006004820152602401610abd565b611237838383611c54565b6000546001600160a01b03163314610cf75760405163118cdaa760e01b8152336004820152602401610abd565b63ffffffff8216600081815260016020908152604091829020849055815192835282018390527f238399d427b947898edb290f5ff0f9109849b1c3ba196a42e35f00c50a54b98b910160405180910390a15050565b60608060006115d8856020013561159186611d7e565b61159e60a0890189612ae8565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611dd892505050565b90935090506000816115eb5760016115ee565b60025b905061160e6116006020880188612668565b8261072760808a018a612ae8565b6004549093506001600160a01b031680156116945760405163043a78eb60e01b81526001600160a01b0382169063043a78eb906116519088908890600401612c84565b602060405180830381865afa15801561166e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116929190612ca9565b505b5050509250929050565b60408051808201909152600080825260208201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663ddc28c586040518060a001604052808863ffffffff16815260200161170189611278565b8152602001878152602001868152602001851515815250306040518363ffffffff1660e01b8152600401611736929190612cc6565b6040805180830381865afa158015611752573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117769190612d6f565b95945050505050565b6001600160a01b0382166117a95760405163ec442f0560e01b815260006004820152602401610abd565b610b6e60008383611c54565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60005b81518110156118dc5761183782828151811061182657611826612ab1565b60200260200101516040015161190c565b81818151811061184957611849612ab1565b6020026020010151604001516003600084848151811061186b5761186b612ab1565b60200260200101516000015163ffffffff1663ffffffff16815260200190815260200160002060008484815181106118a5576118a5612ab1565b60200260200101516020015161ffff1661ffff16815260200190815260200160002090816118d39190612ddb565b50600101611808565b507fbe4864a8e820971c0247f5992e2da559595f7bf076a21cb5928d443d2a13b67481604051610cda9190612e9a565b600281015161ffff8116600314610b6e5781604051639a6d49cd60e01b8152600401610abd91906121fd565b611940612166565b604080518082019091526000808252602082015260008061197733604089013560608a013561197260208c018c612668565b611e52565b91509150600080611988898461157b565b90925090506119b461199d60208b018b612668565b83836119ae368d90038d018d612f25565b8b611e78565b60408051808201909152858152602080820186905282519298509096503391907f85496b760a4b7f8d66384b9df21b381f5d1b1e79f229a47aaf4c232edc2fe59a90611a02908d018d612668565b6040805163ffffffff909216825260208201899052810187905260600160405180910390a350505050935093915050565b6001600160a01b038416611a5d5760405163e602df0560e01b815260006004820152602401610abd565b6001600160a01b038316611a8757604051634a1406b160e11b815260006004820152602401610abd565b6001600160a01b038085166000908152600660209081526040808320938716835292905220829055801561149457826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92584604051611afa91815260200190565b60405180910390a350505050565b60007f0000000000000000000000000000000000000000000000000000000000000000611b358184612f6d565b6108df9190612f8f565b6000611b4e6020828486612a22565b610b5591612fa6565b6000611b67602860208486612a22565b611b7091612fc4565b60c01c9392505050565b60006108df7f00000000000000000000000000000000000000000000000000000000000000006001600160401b038416612f8f565b60006001600160a01b038416611bc55761dead93505b611bcf848461177f565b509092915050565b6060611be68260288186612a22565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929695505050505050565b606084848484604051602001611c3b9493929190612ff4565b6040516020818303038152906040529050949350505050565b6001600160a01b038316611c7f578060076000828254611c749190613043565b90915550611cf19050565b6001600160a01b03831660009081526005602052604090205481811015611cd25760405163391434e360e21b81526001600160a01b03851660048201526024810182905260448101839052606401610abd565b6001600160a01b03841660009081526005602052604090209082900390555b6001600160a01b038216611d0d57600780548290039055611d2c565b6001600160a01b03821660009081526005602052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051611d7191815260200190565b60405180910390a3505050565b600080611dab7f000000000000000000000000000000000000000000000000000000000000000084612f6d565b90506001600160401b038111156108df5760405163e2ce941360e01b815260048101829052602401610abd565b8051606090151580611e21578484604051602001611e0d92919091825260c01b6001600160c01b031916602082015260280190565b604051602081830303815290604052611e48565b84843385604051602001611e389493929190613056565b6040516020818303038152906040525b9150935093915050565b600080611e6085858561123c565b9092509050611e6f8683611f83565b94509492505050565b611e80612166565b6000611e8f8460000151611fb9565b602085015190915015611ea957611ea98460200151611fe1565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316632637a450826040518060a001604052808b63ffffffff168152602001611ef98c611278565b81526020018a815260200189815260200160008960200151111515815250866040518463ffffffff1660e01b8152600401611f35929190612cc6565b60806040518083038185885af1158015611f53573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190611f789190613099565b979650505050505050565b6001600160a01b038216611fad57604051634b637e8f60e11b815260006004820152602401610abd565b610b6e82600083611c54565b6000813414611fdd576040516304fb820960e51b8152346004820152602401610abd565b5090565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663e4fe1d946040518163ffffffff1660e01b8152600401602060405180830381865afa158015612041573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120659190612820565b90506001600160a01b03811661208e576040516329b99a9560e11b815260040160405180910390fd5b610b6e6001600160a01b038216337f0000000000000000000000000000000000000000000000000000000000000000856120cc8484848460016120f4565b61149457604051635274afe760e01b81526001600160a01b0385166004820152602401610abd565b6040516323b872dd60e01b60008181526001600160a01b038781166004528616602452604485905291602083606481808c5af192506001600051148316612154578383151615612147573d6000823e3d81fd5b6000883b113d1516831692505b60405250600060605295945050505050565b60405180606001604052806000801916815260200160006001600160401b031681526020016121a8604051806040016040528060008152602001600081525090565b905290565b60005b838110156121c85781810151838201526020016121b0565b50506000910152565b600081518084526121e98160208601602086016121ad565b601f01601f19169290920160200192915050565b602081526000610b5560208301846121d1565b6001600160a01b03811681146111f157600080fd5b6000806040838503121561223857600080fd5b823561224381612210565b946020939093013593505050565b600060e0828403121561226357600080fd5b50919050565b60006020828403121561227b57600080fd5b81356001600160401b0381111561229157600080fd5b610d0684828501612251565b8351815260208085015190820152600060a08201604060a0604085015281865180845260c08601915060c08160051b8701019350602080890160005b838110156123185788870360bf19018552815180518852830151838801879052612305878901826121d1565b97505093820193908201906001016122d9565b50508751606088015250505060208501516080850152509050610d06565b60006060828403121561226357600080fd5b60008083601f84011261235a57600080fd5b5081356001600160401b0381111561237157600080fd5b60208301915083602082850101111561238957600080fd5b9250929050565b600080600080600080600060e0888a0312156123ab57600080fd5b6123b58989612336565b96506060880135955060808801356001600160401b03808211156123d857600080fd5b6123e48b838c01612348565b909750955060a08a013591506123f982612210565b90935060c0890135908082111561240f57600080fd5b5061241c8a828b01612348565b989b979a50959850939692959293505050565b60008060006060848603121561244457600080fd5b833561244f81612210565b9250602084013561245f81612210565b929592945050506040919091013590565b803563ffffffff8116811461248457600080fd5b919050565b6000806040838503121561249c57600080fd5b61224383612470565b80151581146111f157600080fd5b600080604083850312156124c657600080fd5b82356001600160401b038111156124dc57600080fd5b6124e885828601612251565b92505060208301356124f9816124a5565b809150509250929050565b8151815260208083015190820152604081016108df565b803561ffff8116811461248457600080fd5b6000806040838503121561254057600080fd5b61254983612470565b91506125576020840161251b565b90509250929050565b60006020828403121561257257600080fd5b8135610b5581612210565b60008060008060a0858703121561259357600080fd5b61259d8686612336565b935060608501356001600160401b038111156125b857600080fd5b6125c487828801612348565b90945092505060808501356125d881612210565b939692955090935050565b60008083601f8401126125f557600080fd5b5081356001600160401b0381111561260c57600080fd5b6020830191508360208260051b850101111561238957600080fd5b6000806020838503121561263a57600080fd5b82356001600160401b0381111561265057600080fd5b61265c858286016125e3565b90969095509350505050565b60006020828403121561267a57600080fd5b610b5582612470565b6000806000806060858703121561269957600080fd5b6126a285612470565b93506126b06020860161251b565b925060408501356001600160401b038111156126cb57600080fd5b6126d787828801612348565b95989497509550505050565b600080600083850360808112156126f957600080fd5b84356001600160401b0381111561270f57600080fd5b61271b87828801612251565b9450506040601f198201121561273057600080fd5b50602084019150606084013561274581612210565b809150509250925092565b600060c082019050835182526001600160401b036020850151166020830152604084015161278b604084018280518252602090810151910152565b5082516080830152602083015160a0830152610b55565b600080604083850312156127b557600080fd5b82356127c081612210565b915060208301356124f981612210565b6000606082840312156127e257600080fd5b610b558383612336565b600181811c9082168061280057607f821691505b60208210810361226357634e487b7160e01b600052602260045260246000fd5b60006020828403121561283257600080fd5b8151610b5581612210565b60006020828403121561284f57600080fd5b5051919050565b634e487b7160e01b600052604160045260246000fd5b604051606081016001600160401b038111828210171561288e5761288e612856565b60405290565b604080519081016001600160401b038111828210171561288e5761288e612856565b604051601f8201601f191681016001600160401b03811182821017156128de576128de612856565b604052919050565b60006001600160401b038211156128ff576128ff612856565b50601f01601f191660200190565b60006001600160401b038084111561292757612927612856565b8360051b60206129388183016128b6565b86815291850191818101903684111561295057600080fd5b865b84811015612a165780358681111561296a5760008081fd5b8801606036829003121561297e5760008081fd5b61298661286c565b61298f82612470565b815261299c86830161251b565b86820152604080830135898111156129b45760008081fd5b929092019136601f8401126129c95760008081fd5b82356129dc6129d7826128e6565b6128b6565b81815236898387010111156129f15760008081fd5b818986018a830137600091810189019190915290820152845250918301918301612952565b50979650505050505050565b60008085851115612a3257600080fd5b83861115612a3f57600080fd5b5050820193919092039150565b60008451612a5e8184602089016121ad565b8201838582376000930192835250909392505050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b602081526000610d06602083018486612a74565b634e487b7160e01b600052603260045260246000fd5b6000823561013e19833603018112612ade57600080fd5b9190910192915050565b6000808335601e19843603018112612aff57600080fd5b8301803591506001600160401b03821115612b1957600080fd5b60200191503681900382131561238957600080fd5b6001600160401b03811681146111f157600080fd5b63ffffffff612b5189612470565b1681526020880135602082015260006040890135612b6e81612b2e565b6001600160401b03811660408401525087606083015260e06080830152612b9960e083018789612a74565b6001600160a01b03861660a084015282810360c0840152612bbb818587612a74565b9a9950505050505050505050565b600060208284031215612bdb57600080fd5b81516001600160401b03811115612bf157600080fd5b8201601f81018413612c0257600080fd5b8051612c106129d7826128e6565b818152856020838501011115612c2557600080fd5b6117768260208301602086016121ad565b600060208284031215612c4857600080fd5b8135610b5581612b2e565b60018060a01b038516815283602082015261ffff83166040820152608060608201526000610bcf60808301846121d1565b604081526000612c9760408301856121d1565b828103602084015261177681856121d1565b600060208284031215612cbb57600080fd5b8151610b55816124a5565b6040815263ffffffff8351166040820152602083015160608201526000604084015160a06080840152612cfc60e08401826121d1565b90506060850151603f198483030160a0850152612d1982826121d1565b60809690960151151560c08501525050506001600160a01b039190911660209091015290565b600060408284031215612d5157600080fd5b612d59612894565b9050815181526020820151602082015292915050565b600060408284031215612d8157600080fd5b610b558383612d3f565b601f821115611237576000816000526020600020601f850160051c81016020861015612db45750805b601f850160051c820191505b81811015612dd357828155600101612dc0565b505050505050565b81516001600160401b03811115612df457612df4612856565b612e0881612e0284546127ec565b84612d8b565b602080601f831160018114612e3d5760008415612e255750858301515b600019600386901b1c1916600185901b178555612dd3565b600085815260208120601f198616915b82811015612e6c57888601518255948401946001909101908401612e4d565b5085821015612e8a5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b600060208083018184528085518083526040925060408601915060408160051b87010184880160005b83811015612f1757888303603f190185528151805163ffffffff1684528781015161ffff16888501528601516060878501819052612f03818601836121d1565b968901969450505090860190600101612ec3565b509098975050505050505050565b600060408284031215612f3757600080fd5b612f3f612894565b82358152602083013560208201528091505092915050565b634e487b7160e01b600052601160045260246000fd5b600082612f8a57634e487b7160e01b600052601260045260246000fd5b500490565b80820281158282048414176108df576108df612f57565b803560208310156108df57600019602084900360031b1b1692915050565b6001600160c01b03198135818116916008851015612fec5780818660080360031b1b83161692505b505092915050565b6001600160401b0360c01b8560c01b16815263ffffffff60e01b8460e01b16600882015282600c8201526000825161303381602c8501602087016121ad565b91909101602c0195945050505050565b808201808211156108df576108df612f57565b8481526001600160401b0360c01b8460c01b166020820152826028820152600082516130898160488501602087016121ad565b9190910160480195945050505050565b6000608082840312156130ab57600080fd5b6130b361286c565b8251815260208301516130c581612b2e565b60208201526130d78460408501612d3f565b6040820152939250505056fea2646970667358221220aa404cddf55107bb4ef2c6beb0224c8f9f889ea8535030db0d36c362777d7cae64736f6c63430008160033", + "deployedBytecode": "0x60806040526004361061025c5760003560e01c8063715018a611610144578063bb0b6a53116100b6578063d045a0dc1161007a578063d045a0dc14610780578063d424388514610793578063dd62ed3e146107b3578063f2fde38b146107f9578063fc0c546a1461048c578063ff7bd03d1461081957600080fd5b8063bb0b6a53146106df578063bc70b3541461070c578063bd815db01461072c578063c7c7f5b31461073f578063ca5eb5e11461076057600080fd5b806395d89b411161010857806395d89b4114610622578063963efcaa146106375780639f68b9641461066b578063a9059cbb1461067f578063b731ea0a1461069f578063b98bd070146106bf57600080fd5b8063715018a6146105805780637d25a05e1461059557806382413eac146105d0578063857749b0146105f05780638da5cb5b1461060457600080fd5b806323b872dd116101dd57806352ae2879116101a157806352ae28791461048c5780635535d4611461049f5780635a0dfe4d146104bf5780635e280f11146104f65780636fc1b31e1461052a57806370a082311461054a57600080fd5b806323b872dd146103dd578063313ce567146103fd5780633400288b1461041f5780633b6f743b1461043f57806340c10f191461046c57600080fd5b8063134d4f2511610224578063134d4f2514610338578063156a0d0f1461036057806317442b701461038757806318160ddd146103a95780631f5e1334146103c857600080fd5b806306fdde0314610261578063095ea7b31461028c5780630d35b415146102bc578063111ecdad146102eb57806313137d6514610323575b600080fd5b34801561026d57600080fd5b50610276610839565b60405161028391906121fd565b60405180910390f35b34801561029857600080fd5b506102ac6102a7366004612225565b6108cb565b6040519015158152602001610283565b3480156102c857600080fd5b506102dc6102d7366004612269565b6108e5565b6040516102839392919061229d565b3480156102f757600080fd5b5060045461030b906001600160a01b031681565b6040516001600160a01b039091168152602001610283565b610336610331366004612390565b610a76565b005b34801561034457600080fd5b5061034d600281565b60405161ffff9091168152602001610283565b34801561036c57600080fd5b506040805162b9270b60e21b81526001602082015201610283565b34801561039357600080fd5b5060408051600181526002602082015201610283565b3480156103b557600080fd5b506007545b604051908152602001610283565b3480156103d457600080fd5b5061034d600181565b3480156103e957600080fd5b506102ac6103f836600461242f565b610b36565b34801561040957600080fd5b5060125b60405160ff9091168152602001610283565b34801561042b57600080fd5b5061033661043a366004612489565b610b5c565b34801561044b57600080fd5b5061045f61045a3660046124b3565b610b72565b6040516102839190612504565b34801561047857600080fd5b50610336610487366004612225565b610bd9565b34801561049857600080fd5b503061030b565b3480156104ab57600080fd5b506102766104ba36600461252d565b610be3565b3480156104cb57600080fd5b506102ac6104da366004612489565b63ffffffff919091166000908152600160205260409020541490565b34801561050257600080fd5b5061030b7f000000000000000000000000000000000000000000000000000000000000000081565b34801561053657600080fd5b50610336610545366004612560565b610c88565b34801561055657600080fd5b506103ba610565366004612560565b6001600160a01b031660009081526005602052604090205490565b34801561058c57600080fd5b50610336610ce5565b3480156105a157600080fd5b506105b86105b0366004612489565b600092915050565b6040516001600160401b039091168152602001610283565b3480156105dc57600080fd5b506102ac6105eb36600461257d565b610cf9565b3480156105fc57600080fd5b50600661040d565b34801561061057600080fd5b506000546001600160a01b031661030b565b34801561062e57600080fd5b50610276610d0e565b34801561064357600080fd5b506103ba7f000000000000000000000000000000000000000000000000000000000000000081565b34801561067757600080fd5b5060006102ac565b34801561068b57600080fd5b506102ac61069a366004612225565b610d1d565b3480156106ab57600080fd5b5060025461030b906001600160a01b031681565b3480156106cb57600080fd5b506103366106da366004612627565b610d2b565b3480156106eb57600080fd5b506103ba6106fa366004612668565b60016020526000908152604090205481565b34801561071857600080fd5b50610276610727366004612683565b610d45565b61033661073a366004612627565b610eed565b61075261074d3660046126e3565b611077565b604051610283929190612750565b34801561076c57600080fd5b5061033661077b366004612560565b6110ab565b61033661078e366004612390565b611131565b34801561079f57600080fd5b506103366107ae366004612560565b611160565b3480156107bf57600080fd5b506103ba6107ce3660046127a2565b6001600160a01b03918216600090815260066020908152604080832093909416825291909152205490565b34801561080557600080fd5b50610336610814366004612560565b6111b6565b34801561082557600080fd5b506102ac6108343660046127d0565b6111f4565b606060088054610848906127ec565b80601f0160208091040260200160405190810160405280929190818152602001828054610874906127ec565b80156108c15780601f10610896576101008083540402835291602001916108c1565b820191906000526020600020905b8154815290600101906020018083116108a457829003601f168201915b5050505050905090565b6000336108d981858561122a565b60019150505b92915050565b60408051808201909152600080825260208201526060610918604051806040016040528060008152602001600081525090565b600080306001600160a01b031663fc0c546a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610959573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061097d9190612820565b6001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109ba573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109de919061283d565b60408051808201825284815260208082018490528251600080825291810190935290975091925090610a33565b604080518082019091526000815260606020820152815260200190600190039081610a0b5790505b509350600080610a58604089013560608a0135610a5360208c018c612668565b61123c565b60408051808201909152918252602082015296989597505050505050565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163314610ac6576040516391ac5e4f60e01b81523360048201526024015b60405180910390fd5b60208701803590610ae090610adb908a612668565b611278565b14610b1e57610af26020880188612668565b60405163309afaf360e21b815263ffffffff909116600482015260208801356024820152604401610abd565b610b2d878787878787876112b4565b50505050505050565b600033610b4485828561141b565b610b4f85858561149a565b60019150505b9392505050565b610b646114f9565b610b6e8282611526565b5050565b60408051808201909152600080825260208201526000610ba260408501356060860135610a536020880188612668565b915050600080610bb2868461157b565b9092509050610bcf610bc76020880188612668565b83838861169e565b9695505050505050565b610b6e828261177f565b600360209081526000928352604080842090915290825290208054610c07906127ec565b80601f0160208091040260200160405190810160405280929190818152602001828054610c33906127ec565b8015610c805780601f10610c5557610100808354040283529160200191610c80565b820191906000526020600020905b815481529060010190602001808311610c6357829003601f168201915b505050505081565b610c906114f9565b600480546001600160a01b0319166001600160a01b0383169081179091556040519081527ff0be4f1e87349231d80c36b33f9e8639658eeaf474014dee15a3e6a4d4414197906020015b60405180910390a150565b610ced6114f9565b610cf760006117b5565b565b6001600160a01b03811630145b949350505050565b606060098054610848906127ec565b6000336108d981858561149a565b610d336114f9565b610b6e610d40828461290d565b611805565b63ffffffff8416600090815260036020908152604080832061ffff87168452909152812080546060929190610d79906127ec565b80601f0160208091040260200160405190810160405280929190818152602001828054610da5906127ec565b8015610df25780601f10610dc757610100808354040283529160200191610df2565b820191906000526020600020905b815481529060010190602001808311610dd557829003601f168201915b505050505090508051600003610e425783838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929450610d069350505050565b6000839003610e52579050610d06565b60028310610ed057610e9984848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061190c92505050565b80610ea78460028188612a22565b604051602001610eb993929190612a4c565b604051602081830303815290604052915050610d06565b8383604051639a6d49cd60e01b8152600401610abd929190612a9d565b60005b81811015610ff65736838383818110610f0b57610f0b612ab1565b9050602002810190610f1d9190612ac7565b9050610f50610f2f6020830183612668565b602083013563ffffffff919091166000908152600160205260409020541490565b610f5a5750610fee565b3063d045a0dc60c08301358360a0810135610f79610100830183612ae8565b610f8a610100890160e08a01612560565b610f986101208a018a612ae8565b6040518963ffffffff1660e01b8152600401610fba9796959493929190612b43565b6000604051808303818588803b158015610fd357600080fd5b505af1158015610fe7573d6000803e3d6000fd5b5050505050505b600101610ef0565b50336001600160a01b0316638e9e70996040518163ffffffff1660e01b8152600401600060405180830381865afa158015611035573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261105d9190810190612bc9565b604051638351eea760e01b8152600401610abd91906121fd565b61107f612166565b604080518082019091526000808252602082015261109e858585611938565b915091505b935093915050565b6110b36114f9565b60405163ca5eb5e160e01b81526001600160a01b0382811660048301527f0000000000000000000000000000000000000000000000000000000000000000169063ca5eb5e190602401600060405180830381600087803b15801561111657600080fd5b505af115801561112a573d6000803e3d6000fd5b5050505050565b3330146111515760405163029a949d60e31b815260040160405180910390fd5b610b2d87878787878787610b1e565b6111686114f9565b600280546001600160a01b0319166001600160a01b0383169081179091556040519081527fd48d879cef83a1c0bdda516f27b13ddb1b3f8bbac1c9e1511bb2a659c242776090602001610cda565b6111be6114f9565b6001600160a01b0381166111e857604051631e4fbdf760e01b815260006004820152602401610abd565b6111f1816117b5565b50565b600060208201803590600190839061120c9086612668565b63ffffffff1681526020810191909152604001600020541492915050565b6112378383836001611a33565b505050565b60008061124885611b08565b9150819050838110156110a3576040516371c4efed60e01b81526004810182905260248101859052604401610abd565b63ffffffff8116600090815260016020526040812054806108df5760405163f6ff4fb760e01b815263ffffffff84166004820152602401610abd565b60006112c66112c38787611b3f565b90565b905060006112f2826112e06112db8a8a611b57565b611b7a565b6112ed60208d018d612668565b611baf565b905060288611156113b957600061132f61131260608c0160408d01612c36565b61131f60208d018d612668565b8461132a8c8c611bd7565b611c22565b604051633e5ac80960e11b81529091506001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690637cb59012906113859086908d906000908790600401612c53565b600060405180830381600087803b15801561139f57600080fd5b505af11580156113b3573d6000803e3d6000fd5b50505050505b6001600160a01b038216887fefed6d3500546b29533b128a29e3a94d70788727f0507505ac12eaf2e578fd9c6113f260208d018d612668565b6040805163ffffffff9092168252602082018690520160405180910390a3505050505050505050565b6001600160a01b03838116600090815260066020908152604080832093861683529290522054600019811015611494578181101561148557604051637dc7a0d960e11b81526001600160a01b03841660048201526024810182905260448101839052606401610abd565b61149484848484036000611a33565b50505050565b6001600160a01b0383166114c457604051634b637e8f60e11b815260006004820152602401610abd565b6001600160a01b0382166114ee5760405163ec442f0560e01b815260006004820152602401610abd565b611237838383611c54565b6000546001600160a01b03163314610cf75760405163118cdaa760e01b8152336004820152602401610abd565b63ffffffff8216600081815260016020908152604091829020849055815192835282018390527f238399d427b947898edb290f5ff0f9109849b1c3ba196a42e35f00c50a54b98b910160405180910390a15050565b60608060006115d8856020013561159186611d7e565b61159e60a0890189612ae8565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611dd892505050565b90935090506000816115eb5760016115ee565b60025b905061160e6116006020880188612668565b8261072760808a018a612ae8565b6004549093506001600160a01b031680156116945760405163043a78eb60e01b81526001600160a01b0382169063043a78eb906116519088908890600401612c84565b602060405180830381865afa15801561166e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116929190612ca9565b505b5050509250929050565b60408051808201909152600080825260208201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663ddc28c586040518060a001604052808863ffffffff16815260200161170189611278565b8152602001878152602001868152602001851515815250306040518363ffffffff1660e01b8152600401611736929190612cc6565b6040805180830381865afa158015611752573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117769190612d6f565b95945050505050565b6001600160a01b0382166117a95760405163ec442f0560e01b815260006004820152602401610abd565b610b6e60008383611c54565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60005b81518110156118dc5761183782828151811061182657611826612ab1565b60200260200101516040015161190c565b81818151811061184957611849612ab1565b6020026020010151604001516003600084848151811061186b5761186b612ab1565b60200260200101516000015163ffffffff1663ffffffff16815260200190815260200160002060008484815181106118a5576118a5612ab1565b60200260200101516020015161ffff1661ffff16815260200190815260200160002090816118d39190612ddb565b50600101611808565b507fbe4864a8e820971c0247f5992e2da559595f7bf076a21cb5928d443d2a13b67481604051610cda9190612e9a565b600281015161ffff8116600314610b6e5781604051639a6d49cd60e01b8152600401610abd91906121fd565b611940612166565b604080518082019091526000808252602082015260008061197733604089013560608a013561197260208c018c612668565b611e52565b91509150600080611988898461157b565b90925090506119b461199d60208b018b612668565b83836119ae368d90038d018d612f25565b8b611e78565b60408051808201909152858152602080820186905282519298509096503391907f85496b760a4b7f8d66384b9df21b381f5d1b1e79f229a47aaf4c232edc2fe59a90611a02908d018d612668565b6040805163ffffffff909216825260208201899052810187905260600160405180910390a350505050935093915050565b6001600160a01b038416611a5d5760405163e602df0560e01b815260006004820152602401610abd565b6001600160a01b038316611a8757604051634a1406b160e11b815260006004820152602401610abd565b6001600160a01b038085166000908152600660209081526040808320938716835292905220829055801561149457826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92584604051611afa91815260200190565b60405180910390a350505050565b60007f0000000000000000000000000000000000000000000000000000000000000000611b358184612f6d565b6108df9190612f8f565b6000611b4e6020828486612a22565b610b5591612fa6565b6000611b67602860208486612a22565b611b7091612fc4565b60c01c9392505050565b60006108df7f00000000000000000000000000000000000000000000000000000000000000006001600160401b038416612f8f565b60006001600160a01b038416611bc55761dead93505b611bcf848461177f565b509092915050565b6060611be68260288186612a22565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929695505050505050565b606084848484604051602001611c3b9493929190612ff4565b6040516020818303038152906040529050949350505050565b6001600160a01b038316611c7f578060076000828254611c749190613043565b90915550611cf19050565b6001600160a01b03831660009081526005602052604090205481811015611cd25760405163391434e360e21b81526001600160a01b03851660048201526024810182905260448101839052606401610abd565b6001600160a01b03841660009081526005602052604090209082900390555b6001600160a01b038216611d0d57600780548290039055611d2c565b6001600160a01b03821660009081526005602052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051611d7191815260200190565b60405180910390a3505050565b600080611dab7f000000000000000000000000000000000000000000000000000000000000000084612f6d565b90506001600160401b038111156108df5760405163e2ce941360e01b815260048101829052602401610abd565b8051606090151580611e21578484604051602001611e0d92919091825260c01b6001600160c01b031916602082015260280190565b604051602081830303815290604052611e48565b84843385604051602001611e389493929190613056565b6040516020818303038152906040525b9150935093915050565b600080611e6085858561123c565b9092509050611e6f8683611f83565b94509492505050565b611e80612166565b6000611e8f8460000151611fb9565b602085015190915015611ea957611ea98460200151611fe1565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316632637a450826040518060a001604052808b63ffffffff168152602001611ef98c611278565b81526020018a815260200189815260200160008960200151111515815250866040518463ffffffff1660e01b8152600401611f35929190612cc6565b60806040518083038185885af1158015611f53573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190611f789190613099565b979650505050505050565b6001600160a01b038216611fad57604051634b637e8f60e11b815260006004820152602401610abd565b610b6e82600083611c54565b6000813414611fdd576040516304fb820960e51b8152346004820152602401610abd565b5090565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663e4fe1d946040518163ffffffff1660e01b8152600401602060405180830381865afa158015612041573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120659190612820565b90506001600160a01b03811661208e576040516329b99a9560e11b815260040160405180910390fd5b610b6e6001600160a01b038216337f0000000000000000000000000000000000000000000000000000000000000000856120cc8484848460016120f4565b61149457604051635274afe760e01b81526001600160a01b0385166004820152602401610abd565b6040516323b872dd60e01b60008181526001600160a01b038781166004528616602452604485905291602083606481808c5af192506001600051148316612154578383151615612147573d6000823e3d81fd5b6000883b113d1516831692505b60405250600060605295945050505050565b60405180606001604052806000801916815260200160006001600160401b031681526020016121a8604051806040016040528060008152602001600081525090565b905290565b60005b838110156121c85781810151838201526020016121b0565b50506000910152565b600081518084526121e98160208601602086016121ad565b601f01601f19169290920160200192915050565b602081526000610b5560208301846121d1565b6001600160a01b03811681146111f157600080fd5b6000806040838503121561223857600080fd5b823561224381612210565b946020939093013593505050565b600060e0828403121561226357600080fd5b50919050565b60006020828403121561227b57600080fd5b81356001600160401b0381111561229157600080fd5b610d0684828501612251565b8351815260208085015190820152600060a08201604060a0604085015281865180845260c08601915060c08160051b8701019350602080890160005b838110156123185788870360bf19018552815180518852830151838801879052612305878901826121d1565b97505093820193908201906001016122d9565b50508751606088015250505060208501516080850152509050610d06565b60006060828403121561226357600080fd5b60008083601f84011261235a57600080fd5b5081356001600160401b0381111561237157600080fd5b60208301915083602082850101111561238957600080fd5b9250929050565b600080600080600080600060e0888a0312156123ab57600080fd5b6123b58989612336565b96506060880135955060808801356001600160401b03808211156123d857600080fd5b6123e48b838c01612348565b909750955060a08a013591506123f982612210565b90935060c0890135908082111561240f57600080fd5b5061241c8a828b01612348565b989b979a50959850939692959293505050565b60008060006060848603121561244457600080fd5b833561244f81612210565b9250602084013561245f81612210565b929592945050506040919091013590565b803563ffffffff8116811461248457600080fd5b919050565b6000806040838503121561249c57600080fd5b61224383612470565b80151581146111f157600080fd5b600080604083850312156124c657600080fd5b82356001600160401b038111156124dc57600080fd5b6124e885828601612251565b92505060208301356124f9816124a5565b809150509250929050565b8151815260208083015190820152604081016108df565b803561ffff8116811461248457600080fd5b6000806040838503121561254057600080fd5b61254983612470565b91506125576020840161251b565b90509250929050565b60006020828403121561257257600080fd5b8135610b5581612210565b60008060008060a0858703121561259357600080fd5b61259d8686612336565b935060608501356001600160401b038111156125b857600080fd5b6125c487828801612348565b90945092505060808501356125d881612210565b939692955090935050565b60008083601f8401126125f557600080fd5b5081356001600160401b0381111561260c57600080fd5b6020830191508360208260051b850101111561238957600080fd5b6000806020838503121561263a57600080fd5b82356001600160401b0381111561265057600080fd5b61265c858286016125e3565b90969095509350505050565b60006020828403121561267a57600080fd5b610b5582612470565b6000806000806060858703121561269957600080fd5b6126a285612470565b93506126b06020860161251b565b925060408501356001600160401b038111156126cb57600080fd5b6126d787828801612348565b95989497509550505050565b600080600083850360808112156126f957600080fd5b84356001600160401b0381111561270f57600080fd5b61271b87828801612251565b9450506040601f198201121561273057600080fd5b50602084019150606084013561274581612210565b809150509250925092565b600060c082019050835182526001600160401b036020850151166020830152604084015161278b604084018280518252602090810151910152565b5082516080830152602083015160a0830152610b55565b600080604083850312156127b557600080fd5b82356127c081612210565b915060208301356124f981612210565b6000606082840312156127e257600080fd5b610b558383612336565b600181811c9082168061280057607f821691505b60208210810361226357634e487b7160e01b600052602260045260246000fd5b60006020828403121561283257600080fd5b8151610b5581612210565b60006020828403121561284f57600080fd5b5051919050565b634e487b7160e01b600052604160045260246000fd5b604051606081016001600160401b038111828210171561288e5761288e612856565b60405290565b604080519081016001600160401b038111828210171561288e5761288e612856565b604051601f8201601f191681016001600160401b03811182821017156128de576128de612856565b604052919050565b60006001600160401b038211156128ff576128ff612856565b50601f01601f191660200190565b60006001600160401b038084111561292757612927612856565b8360051b60206129388183016128b6565b86815291850191818101903684111561295057600080fd5b865b84811015612a165780358681111561296a5760008081fd5b8801606036829003121561297e5760008081fd5b61298661286c565b61298f82612470565b815261299c86830161251b565b86820152604080830135898111156129b45760008081fd5b929092019136601f8401126129c95760008081fd5b82356129dc6129d7826128e6565b6128b6565b81815236898387010111156129f15760008081fd5b818986018a830137600091810189019190915290820152845250918301918301612952565b50979650505050505050565b60008085851115612a3257600080fd5b83861115612a3f57600080fd5b5050820193919092039150565b60008451612a5e8184602089016121ad565b8201838582376000930192835250909392505050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b602081526000610d06602083018486612a74565b634e487b7160e01b600052603260045260246000fd5b6000823561013e19833603018112612ade57600080fd5b9190910192915050565b6000808335601e19843603018112612aff57600080fd5b8301803591506001600160401b03821115612b1957600080fd5b60200191503681900382131561238957600080fd5b6001600160401b03811681146111f157600080fd5b63ffffffff612b5189612470565b1681526020880135602082015260006040890135612b6e81612b2e565b6001600160401b03811660408401525087606083015260e06080830152612b9960e083018789612a74565b6001600160a01b03861660a084015282810360c0840152612bbb818587612a74565b9a9950505050505050505050565b600060208284031215612bdb57600080fd5b81516001600160401b03811115612bf157600080fd5b8201601f81018413612c0257600080fd5b8051612c106129d7826128e6565b818152856020838501011115612c2557600080fd5b6117768260208301602086016121ad565b600060208284031215612c4857600080fd5b8135610b5581612b2e565b60018060a01b038516815283602082015261ffff83166040820152608060608201526000610bcf60808301846121d1565b604081526000612c9760408301856121d1565b828103602084015261177681856121d1565b600060208284031215612cbb57600080fd5b8151610b55816124a5565b6040815263ffffffff8351166040820152602083015160608201526000604084015160a06080840152612cfc60e08401826121d1565b90506060850151603f198483030160a0850152612d1982826121d1565b60809690960151151560c08501525050506001600160a01b039190911660209091015290565b600060408284031215612d5157600080fd5b612d59612894565b9050815181526020820151602082015292915050565b600060408284031215612d8157600080fd5b610b558383612d3f565b601f821115611237576000816000526020600020601f850160051c81016020861015612db45750805b601f850160051c820191505b81811015612dd357828155600101612dc0565b505050505050565b81516001600160401b03811115612df457612df4612856565b612e0881612e0284546127ec565b84612d8b565b602080601f831160018114612e3d5760008415612e255750858301515b600019600386901b1c1916600185901b178555612dd3565b600085815260208120601f198616915b82811015612e6c57888601518255948401946001909101908401612e4d565b5085821015612e8a5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b600060208083018184528085518083526040925060408601915060408160051b87010184880160005b83811015612f1757888303603f190185528151805163ffffffff1684528781015161ffff16888501528601516060878501819052612f03818601836121d1565b968901969450505090860190600101612ec3565b509098975050505050505050565b600060408284031215612f3757600080fd5b612f3f612894565b82358152602083013560208201528091505092915050565b634e487b7160e01b600052601160045260246000fd5b600082612f8a57634e487b7160e01b600052601260045260246000fd5b500490565b80820281158282048414176108df576108df612f57565b803560208310156108df57600019602084900360031b1b1692915050565b6001600160c01b03198135818116916008851015612fec5780818660080360031b1b83161692505b505092915050565b6001600160401b0360c01b8560c01b16815263ffffffff60e01b8460e01b16600882015282600c8201526000825161303381602c8501602087016121ad565b91909101602c0195945050505050565b808201808211156108df576108df612f57565b8481526001600160401b0360c01b8460c01b166020820152826028820152600082516130898160488501602087016121ad565b9190910160480195945050505050565b6000608082840312156130ab57600080fd5b6130b361286c565b8251815260208301516130c581612b2e565b60208201526130d78460408501612d3f565b6040820152939250505056fea2646970667358221220aa404cddf55107bb4ef2c6beb0224c8f9f889ea8535030db0d36c362777d7cae64736f6c63430008160033", + "devdoc": { + "errors": { + "ERC20InsufficientAllowance(address,uint256,uint256)": [ + { + "details": "Indicates a failure with the `spender`’s `allowance`. Used in transfers.", + "params": { + "allowance": "Amount of tokens a `spender` is allowed to operate with.", + "needed": "Minimum amount required to perform a transfer.", + "spender": "Address that may be allowed to operate on tokens without being their owner." + } + } + ], + "ERC20InsufficientBalance(address,uint256,uint256)": [ + { + "details": "Indicates an error related to the current `balance` of a `sender`. Used in transfers.", + "params": { + "balance": "Current balance for the interacting account.", + "needed": "Minimum amount required to perform a transfer.", + "sender": "Address whose tokens are being transferred." + } + } + ], + "ERC20InvalidApprover(address)": [ + { + "details": "Indicates a failure with the `approver` of a token to be approved. Used in approvals.", + "params": { + "approver": "Address initiating an approval operation." + } + } + ], + "ERC20InvalidReceiver(address)": [ + { + "details": "Indicates a failure with the token `receiver`. Used in transfers.", + "params": { + "receiver": "Address to which tokens are being transferred." + } + } + ], + "ERC20InvalidSender(address)": [ + { + "details": "Indicates a failure with the token `sender`. Used in transfers.", + "params": { + "sender": "Address whose tokens are being transferred." + } + } + ], + "ERC20InvalidSpender(address)": [ + { + "details": "Indicates a failure with the `spender` to be approved. Used in approvals.", + "params": { + "spender": "Address that may be allowed to operate on tokens without being their owner." + } + } + ], + "OwnableInvalidOwner(address)": [ + { + "details": "The owner is not a valid owner account. (eg. `address(0)`)" + } + ], + "OwnableUnauthorizedAccount(address)": [ + { + "details": "The caller account is not authorized to perform an operation." + } + ], + "SafeERC20FailedOperation(address)": [ + { + "details": "An operation with an ERC-20 token failed." + } + ] + }, + "events": { + "Approval(address,address,uint256)": { + "details": "Emitted when the allowance of a `spender` for an `owner` is set by a call to {approve}. `value` is the new allowance." + }, + "PreCrimeSet(address)": { + "details": "Emitted when the preCrime contract address is set.", + "params": { + "preCrimeAddress": "The address of the preCrime contract." + } + }, + "Transfer(address,address,uint256)": { + "details": "Emitted when `value` tokens are moved from one account (`from`) to another (`to`). Note that `value` may be zero." + } + }, + "kind": "dev", + "methods": { + "allowInitializePath((uint32,bytes32,uint64))": { + "details": "This indicates to the endpoint that the OApp has enabled msgs for this particular path to be received.This defaults to assuming if a peer has been set, its initialized. Can be overridden by the OApp if there is other logic to determine this.", + "params": { + "origin": "The origin information containing the source endpoint and sender address." + }, + "returns": { + "_0": "Whether the path has been initialized." + } + }, + "allowance(address,address)": { + "details": "Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` through {transferFrom}. This is zero by default. This value changes when {approve} or {transferFrom} are called." + }, + "approvalRequired()": { + "details": "In the case of OFT where the contract IS the token, approval is NOT required.", + "returns": { + "_0": "requiresApproval Needs approval of the underlying token implementation." + } + }, + "approve(address,uint256)": { + "details": "See {IERC20-approve}. NOTE: If `value` is the maximum `uint256`, the allowance is not updated on `transferFrom`. This is semantically equivalent to an infinite approval. Requirements: - `spender` cannot be the zero address." + }, + "balanceOf(address)": { + "details": "Returns the value of tokens owned by `account`." + }, + "combineOptions(uint32,uint16,bytes)": { + "details": "If there is an enforced lzReceive option: - {gasLimit: 200k, msg.value: 1 ether} AND a caller supplies a lzReceive option: {gasLimit: 100k, msg.value: 0.5 ether} - The resulting options will be {gasLimit: 300k, msg.value: 1.5 ether} when the message is executed on the remote lzReceive() function.This presence of duplicated options is handled off-chain in the verifier/executor.", + "params": { + "_eid": "The endpoint ID.", + "_extraOptions": "Additional options passed by the caller.", + "_msgType": "The OAPP message type." + }, + "returns": { + "_0": "options The combination of caller specified options AND enforced options." + } + }, + "decimals()": { + "details": "Returns the number of decimals used to get its user representation. For example, if `decimals` equals `2`, a balance of `505` tokens should be displayed to a user as `5.05` (`505 / 10 ** 2`). Tokens usually opt for a value of 18, imitating the relationship between Ether and Wei. This is the default value returned by this function, unless it's overridden. NOTE: This information is only used for _display_ purposes: it in no way affects any of the arithmetic of the contract, including {IERC20-balanceOf} and {IERC20-transfer}." + }, + "isComposeMsgSender((uint32,bytes32,uint64),bytes,address)": { + "details": "_origin The origin information containing the source endpoint and sender address. - srcEid: The source chain endpoint ID. - sender: The sender address on the src chain. - nonce: The nonce of the message._message The lzReceive payload.Applications can optionally choose to implement separate composeMsg senders that are NOT the bridging layer.The default sender IS the OAppReceiver implementer.", + "params": { + "_sender": "The sender address." + }, + "returns": { + "_0": "isSender Is a valid sender." + } + }, + "isPeer(uint32,bytes32)": { + "details": "Check if the peer is considered 'trusted' by the OApp.Enables OAppPreCrimeSimulator to check whether a potential Inbound Packet is from a trusted source.", + "params": { + "_eid": "The endpoint ID to check.", + "_peer": "The peer to check." + }, + "returns": { + "_0": "Whether the peer passed is considered 'trusted' by the OApp." + } + }, + "lzReceive((uint32,bytes32,uint64),bytes32,bytes,address,bytes)": { + "details": "Entry point for receiving messages or packets from the endpoint.Entry point for receiving msg/packet from the LayerZero endpoint.", + "params": { + "_executor": "The address of the executor for the received message.", + "_extraData": "Additional arbitrary data provided by the corresponding executor.", + "_guid": "The unique identifier for the received LayerZero message.", + "_message": "The payload of the received message.", + "_origin": "The origin information containing the source endpoint and sender address. - srcEid: The source chain endpoint ID. - sender: The sender address on the src chain. - nonce: The nonce of the message." + } + }, + "lzReceiveAndRevert(((uint32,bytes32,uint64),uint32,address,bytes32,uint256,address,bytes,bytes)[])": { + "details": "Interface for pre-crime simulations. Always reverts at the end with the simulation results.WARNING: MUST revert at the end with the simulation results.Gives the preCrime implementation the ability to mock sending packets to the lzReceive function, WITHOUT actually executing them.", + "params": { + "_packets": "An array of InboundPacket objects representing received packets to be delivered." + } + }, + "lzReceiveSimulate((uint32,bytes32,uint64),bytes32,bytes,address,bytes)": { + "details": "Is effectively an internal function because msg.sender must be address(this). Allows resetting the call stack for 'internal' calls.", + "params": { + "_executor": "The executor address for the packet.", + "_extraData": "Additional data for the packet.", + "_guid": "The unique identifier of the packet.", + "_message": "The message payload of the packet.", + "_origin": "The origin information containing the source endpoint and sender address. - srcEid: The source chain endpoint ID. - sender: The sender address on the src chain. - nonce: The nonce of the message." + } + }, + "mint(address,uint256)": { + "details": "Deliberately unguarded: the demo mints to itself before each send, and a testnet OFT with no way to obtain tokens cannot be used to exercise the DVN at all. Do NOT ship this to a network where the token has value. `virtual` because MyOFTMock declares the same function for the hardhat tests." + }, + "name()": { + "details": "Returns the name of the token." + }, + "nextNonce(uint32,bytes32)": { + "details": "_srcEid The source endpoint ID._sender The sender address.The path nonce starts from 1. If 0 is returned it means that there is NO nonce ordered enforcement.Is required by the off-chain executor to determine the OApp expects msg execution is ordered.This is also enforced by the OApp.By default this is NOT enabled. ie. nextNonce is hardcoded to return 0.", + "returns": { + "nonce": "The next nonce." + } + }, + "oApp()": { + "details": "Retrieves the address of the OApp contract.The simulator contract is the base contract for the OApp by default.If the simulator is a separate contract, override this function.", + "returns": { + "_0": "The address of the OApp contract." + } + }, + "oAppVersion()": { + "returns": { + "receiverVersion": "The version of the OAppReceiver.sol implementation.", + "senderVersion": "The version of the OAppSender.sol implementation." + } + }, + "oftVersion()": { + "details": "interfaceId: This specific interface ID is '0x02e49c2c'.version: Indicates a cross-chain compatible msg encoding with other OFTs.If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented. ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)", + "returns": { + "interfaceId": "The interface ID.", + "version": "The version." + } + }, + "owner()": { + "details": "Returns the address of the current owner." + }, + "quoteOFT((uint32,bytes32,uint256,uint256,bytes,bytes,bytes))": { + "params": { + "_sendParam": "The parameters for the send operation." + }, + "returns": { + "oftFeeDetails": "The details of OFT fees.", + "oftLimit": "The OFT limit information.", + "oftReceipt": "The OFT receipt information." + } + }, + "quoteSend((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),bool)": { + "details": "MessagingFee: LayerZero msg fee - nativeFee: The native fee. - lzTokenFee: The lzToken fee.", + "params": { + "_payInLzToken": "Flag indicating whether the caller is paying in the LZ token.", + "_sendParam": "The parameters for the send() operation." + }, + "returns": { + "msgFee": "The calculated LayerZero messaging fee from the send() operation." + } + }, + "renounceOwnership()": { + "details": "Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner." + }, + "send((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),(uint256,uint256),address)": { + "details": "Executes the send operation.MessagingReceipt: LayerZero msg receipt - guid: The unique identifier for the sent message. - nonce: The nonce of the sent message. - fee: The LayerZero fee incurred for the message.", + "params": { + "_fee": "The calculated fee for the send() operation. - nativeFee: The native fee. - lzTokenFee: The lzToken fee.", + "_refundAddress": "The address to receive any excess funds.", + "_sendParam": "The parameters for the send operation." + }, + "returns": { + "msgReceipt": "The receipt for the send operation.", + "oftReceipt": "The OFT receipt information." + } + }, + "setDelegate(address)": { + "details": "Only the owner/admin of the OApp can call this function.Provides the ability for a delegate to set configs, on behalf of the OApp, directly on the Endpoint contract.", + "params": { + "_delegate": "The address of the delegate to be set." + } + }, + "setEnforcedOptions((uint32,uint16,bytes)[])": { + "details": "Sets the enforced options for specific endpoint and message type combinations.Only the owner/admin of the OApp can call this function.Provides a way for the OApp to enforce things like paying for PreCrime, AND/OR minimum dst lzReceive gas amounts etc.These enforced options can vary as the potential options/execution on the remote may differ as per the msgType. eg. Amount of lzReceive() gas necessary to deliver a lzCompose() message adds overhead you dont want to pay if you are only making a standard LayerZero message ie. lzReceive() WITHOUT sendCompose().", + "params": { + "_enforcedOptions": "An array of EnforcedOptionParam structures specifying enforced options." + } + }, + "setMsgInspector(address)": { + "details": "Sets the message inspector address for the OFT.This is an optional contract that can be used to inspect both 'message' and 'options'.Set it to address(0) to disable it, or set it to a contract address to enable it.", + "params": { + "_msgInspector": "The address of the message inspector." + } + }, + "setPeer(uint32,bytes32)": { + "details": "Only the owner/admin of the OApp can call this function.Indicates that the peer is trusted to send LayerZero messages to this OApp.Set this to bytes32(0) to remove the peer address.Peer is a bytes32 to accommodate non-evm chains.", + "params": { + "_eid": "The endpoint ID.", + "_peer": "The address of the peer to be associated with the corresponding endpoint." + } + }, + "setPreCrime(address)": { + "details": "Sets the preCrime contract address.", + "params": { + "_preCrime": "The address of the preCrime contract." + } + }, + "sharedDecimals()": { + "details": "Retrieves the shared decimals of the OFT.Sets an implicit cap on the amount of tokens, over uint64.max() will need some sort of outbound cap / totalSupply cap Lowest common decimal denominator between chains. Defaults to 6 decimal places to provide up to 18,446,744,073,709.551615 units (max uint64). For tokens exceeding this totalSupply(), they will need to override the sharedDecimals function with something smaller. ie. 4 sharedDecimals would be 1,844,674,407,370,955.1615", + "returns": { + "_0": "The shared decimals of the OFT." + } + }, + "symbol()": { + "details": "Returns the symbol of the token, usually a shorter version of the name." + }, + "token()": { + "details": "Retrieves the address of the underlying ERC20 implementation.In the case of OFT, address(this) and erc20 are the same contract.", + "returns": { + "_0": "The address of the OFT token." + } + }, + "totalSupply()": { + "details": "Returns the value of tokens in existence." + }, + "transfer(address,uint256)": { + "details": "See {IERC20-transfer}. Requirements: - `to` cannot be the zero address. - the caller must have a balance of at least `value`." + }, + "transferFrom(address,address,uint256)": { + "details": "See {IERC20-transferFrom}. Skips emitting an {Approval} event indicating an allowance update. This is not required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve]. NOTE: Does not update the allowance if the current allowance is the maximum `uint256`. Requirements: - `from` and `to` cannot be the zero address. - `from` must have a balance of at least `value`. - the caller must have allowance for ``from``'s tokens of at least `value`." + }, + "transferOwnership(address)": { + "details": "Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner." + } + }, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "allowInitializePath((uint32,bytes32,uint64))": { + "notice": "Checks if the path initialization is allowed based on the provided origin." + }, + "approvalRequired()": { + "notice": "Indicates whether the OFT contract requires approval of the 'token()' to send." + }, + "combineOptions(uint32,uint16,bytes)": { + "notice": "Combines options for a given endpoint and message type." + }, + "endpoint()": { + "notice": "Retrieves the LayerZero endpoint associated with the OApp." + }, + "isComposeMsgSender((uint32,bytes32,uint64),bytes,address)": { + "notice": "Indicates whether an address is an approved composeMsg sender to the Endpoint." + }, + "mint(address,uint256)": { + "notice": "Open mint, TESTNET ONLY — same as ToyOFT, so the demo tasks work against either." + }, + "nextNonce(uint32,bytes32)": { + "notice": "Retrieves the next nonce for a given source endpoint and sender address." + }, + "oAppVersion()": { + "notice": "Retrieves the OApp version information." + }, + "oftVersion()": { + "notice": "Retrieves interfaceID and the version of the OFT." + }, + "peers(uint32)": { + "notice": "Retrieves the peer (OApp) associated with a corresponding endpoint." + }, + "quoteOFT((uint32,bytes32,uint256,uint256,bytes,bytes,bytes))": { + "notice": "Provides the fee breakdown and settings data for an OFT. Unused in the default implementation." + }, + "quoteSend((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),bool)": { + "notice": "Provides a quote for the send() operation." + }, + "setDelegate(address)": { + "notice": "Sets the delegate address for the OApp." + }, + "setPeer(uint32,bytes32)": { + "notice": "Sets the peer address (OApp instance) for a corresponding endpoint." + } + }, + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 3861, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "t_address" + }, + { + "astId": 1390, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "peers", + "offset": 0, + "slot": "1", + "type": "t_mapping(t_uint32,t_bytes32)" + }, + { + "astId": 2166, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "preCrime", + "offset": 0, + "slot": "2", + "type": "t_address" + }, + { + "astId": 2006, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "enforcedOptions", + "offset": 0, + "slot": "3", + "type": "t_mapping(t_uint32,t_mapping(t_uint16,t_bytes_storage))" + }, + { + "astId": 2786, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "msgInspector", + "offset": 0, + "slot": "4", + "type": "t_address" + }, + { + "astId": 4250, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "_balances", + "offset": 0, + "slot": "5", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 4256, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "_allowances", + "offset": 0, + "slot": "6", + "type": "t_mapping(t_address,t_mapping(t_address,t_uint256))" + }, + { + "astId": 4258, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "_totalSupply", + "offset": 0, + "slot": "7", + "type": "t_uint256" + }, + { + "astId": 4260, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "_name", + "offset": 0, + "slot": "8", + "type": "t_string_storage" + }, + { + "astId": 4262, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "_symbol", + "offset": 0, + "slot": "9", + "type": "t_string_storage" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_bytes32": { + "encoding": "inplace", + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_bytes_storage": { + "encoding": "bytes", + "label": "bytes", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32", + "value": "t_mapping(t_address,t_uint256)" + }, + "t_mapping(t_address,t_uint256)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_mapping(t_uint16,t_bytes_storage)": { + "encoding": "mapping", + "key": "t_uint16", + "label": "mapping(uint16 => bytes)", + "numberOfBytes": "32", + "value": "t_bytes_storage" + }, + "t_mapping(t_uint32,t_bytes32)": { + "encoding": "mapping", + "key": "t_uint32", + "label": "mapping(uint32 => bytes32)", + "numberOfBytes": "32", + "value": "t_bytes32" + }, + "t_mapping(t_uint32,t_mapping(t_uint16,t_bytes_storage))": { + "encoding": "mapping", + "key": "t_uint32", + "label": "mapping(uint32 => mapping(uint16 => bytes))", + "numberOfBytes": "32", + "value": "t_mapping(t_uint16,t_bytes_storage)" + }, + "t_string_storage": { + "encoding": "bytes", + "label": "string", + "numberOfBytes": "32" + }, + "t_uint16": { + "encoding": "inplace", + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint32": { + "encoding": "inplace", + "label": "uint32", + "numberOfBytes": "4" + } + } + } +} \ No newline at end of file diff --git a/deployments/base-sepolia/MyOFT.json b/deployments/base-sepolia/MyOFT.json new file mode 100644 index 0000000..d4d100a --- /dev/null +++ b/deployments/base-sepolia/MyOFT.json @@ -0,0 +1,2191 @@ +{ + "address": "0x2DC5e5177a172c0FDc7c7d490A5D6D098e822eB7", + "abi": [ + { + "inputs": [ + { + "internalType": "string", + "name": "_name", + "type": "string" + }, + { + "internalType": "string", + "name": "_symbol", + "type": "string" + }, + { + "internalType": "address", + "name": "_lzEndpoint", + "type": "address" + }, + { + "internalType": "address", + "name": "_delegate", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountSD", + "type": "uint256" + } + ], + "name": "AmountSDOverflowed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "allowance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientAllowance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "ERC20InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC20InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "ERC20InvalidSender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "ERC20InvalidSpender", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidDelegate", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidEndpointCall", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidLocalDecimals", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "options", + "type": "bytes" + } + ], + "name": "InvalidOptions", + "type": "error" + }, + { + "inputs": [], + "name": "LzTokenUnavailable", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "eid", + "type": "uint32" + } + ], + "name": "NoPeer", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "msgValue", + "type": "uint256" + } + ], + "name": "NotEnoughNative", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "OnlyEndpoint", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "eid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + } + ], + "name": "OnlyPeer", + "type": "error" + }, + { + "inputs": [], + "name": "OnlySelf", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "result", + "type": "bytes" + } + ], + "name": "SimulationResult", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountLD", + "type": "uint256" + } + ], + "name": "SlippageExceeded", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "eid", + "type": "uint32" + }, + { + "internalType": "uint16", + "name": "msgType", + "type": "uint16" + }, + { + "internalType": "bytes", + "name": "options", + "type": "bytes" + } + ], + "indexed": false, + "internalType": "struct EnforcedOptionParam[]", + "name": "_enforcedOptions", + "type": "tuple[]" + } + ], + "name": "EnforcedOptionSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "inspector", + "type": "address" + } + ], + "name": "MsgInspectorSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "guid", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "srcEid", + "type": "uint32" + }, + { + "indexed": true, + "internalType": "address", + "name": "toAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountReceivedLD", + "type": "uint256" + } + ], + "name": "OFTReceived", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "guid", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "dstEid", + "type": "uint32" + }, + { + "indexed": true, + "internalType": "address", + "name": "fromAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountSentLD", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountReceivedLD", + "type": "uint256" + } + ], + "name": "OFTSent", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint32", + "name": "eid", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "peer", + "type": "bytes32" + } + ], + "name": "PeerSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "preCrimeAddress", + "type": "address" + } + ], + "name": "PreCrimeSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "SEND", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SEND_AND_CALL", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "srcEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "internalType": "struct Origin", + "name": "origin", + "type": "tuple" + } + ], + "name": "allowInitializePath", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "approvalRequired", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_eid", + "type": "uint32" + }, + { + "internalType": "uint16", + "name": "_msgType", + "type": "uint16" + }, + { + "internalType": "bytes", + "name": "_extraOptions", + "type": "bytes" + } + ], + "name": "combineOptions", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimalConversionRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "endpoint", + "outputs": [ + { + "internalType": "contract ILayerZeroEndpointV2", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "eid", + "type": "uint32" + }, + { + "internalType": "uint16", + "name": "msgType", + "type": "uint16" + } + ], + "name": "enforcedOptions", + "outputs": [ + { + "internalType": "bytes", + "name": "enforcedOption", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "srcEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "internalType": "struct Origin", + "name": "", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_sender", + "type": "address" + } + ], + "name": "isComposeMsgSender", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_eid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "_peer", + "type": "bytes32" + } + ], + "name": "isPeer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "srcEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "internalType": "struct Origin", + "name": "_origin", + "type": "tuple" + }, + { + "internalType": "bytes32", + "name": "_guid", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "_message", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_executor", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_extraData", + "type": "bytes" + } + ], + "name": "lzReceive", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "uint32", + "name": "srcEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "internalType": "struct Origin", + "name": "origin", + "type": "tuple" + }, + { + "internalType": "uint32", + "name": "dstEid", + "type": "uint32" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "guid", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "address", + "name": "executor", + "type": "address" + }, + { + "internalType": "bytes", + "name": "message", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "internalType": "struct InboundPacket[]", + "name": "_packets", + "type": "tuple[]" + } + ], + "name": "lzReceiveAndRevert", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "srcEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "internalType": "struct Origin", + "name": "_origin", + "type": "tuple" + }, + { + "internalType": "bytes32", + "name": "_guid", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "_message", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_executor", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_extraData", + "type": "bytes" + } + ], + "name": "lzReceiveSimulate", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "msgInspector", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "nextNonce", + "outputs": [ + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oApp", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oAppVersion", + "outputs": [ + { + "internalType": "uint64", + "name": "senderVersion", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "receiverVersion", + "type": "uint64" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "oftVersion", + "outputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + }, + { + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "eid", + "type": "uint32" + } + ], + "name": "peers", + "outputs": [ + { + "internalType": "bytes32", + "name": "peer", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "preCrime", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "dstEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "to", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amountLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountLD", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "extraOptions", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "composeMsg", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "oftCmd", + "type": "bytes" + } + ], + "internalType": "struct SendParam", + "name": "_sendParam", + "type": "tuple" + } + ], + "name": "quoteOFT", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "minAmountLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxAmountLD", + "type": "uint256" + } + ], + "internalType": "struct OFTLimit", + "name": "oftLimit", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "int256", + "name": "feeAmountLD", + "type": "int256" + }, + { + "internalType": "string", + "name": "description", + "type": "string" + } + ], + "internalType": "struct OFTFeeDetail[]", + "name": "oftFeeDetails", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "amountSentLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountReceivedLD", + "type": "uint256" + } + ], + "internalType": "struct OFTReceipt", + "name": "oftReceipt", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "dstEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "to", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amountLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountLD", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "extraOptions", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "composeMsg", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "oftCmd", + "type": "bytes" + } + ], + "internalType": "struct SendParam", + "name": "_sendParam", + "type": "tuple" + }, + { + "internalType": "bool", + "name": "_payInLzToken", + "type": "bool" + } + ], + "name": "quoteSend", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "nativeFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lzTokenFee", + "type": "uint256" + } + ], + "internalType": "struct MessagingFee", + "name": "msgFee", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "dstEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "to", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amountLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountLD", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "extraOptions", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "composeMsg", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "oftCmd", + "type": "bytes" + } + ], + "internalType": "struct SendParam", + "name": "_sendParam", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "nativeFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lzTokenFee", + "type": "uint256" + } + ], + "internalType": "struct MessagingFee", + "name": "_fee", + "type": "tuple" + }, + { + "internalType": "address", + "name": "_refundAddress", + "type": "address" + } + ], + "name": "send", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "guid", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "nativeFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lzTokenFee", + "type": "uint256" + } + ], + "internalType": "struct MessagingFee", + "name": "fee", + "type": "tuple" + } + ], + "internalType": "struct MessagingReceipt", + "name": "msgReceipt", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "amountSentLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountReceivedLD", + "type": "uint256" + } + ], + "internalType": "struct OFTReceipt", + "name": "oftReceipt", + "type": "tuple" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_delegate", + "type": "address" + } + ], + "name": "setDelegate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "eid", + "type": "uint32" + }, + { + "internalType": "uint16", + "name": "msgType", + "type": "uint16" + }, + { + "internalType": "bytes", + "name": "options", + "type": "bytes" + } + ], + "internalType": "struct EnforcedOptionParam[]", + "name": "_enforcedOptions", + "type": "tuple[]" + } + ], + "name": "setEnforcedOptions", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_msgInspector", + "type": "address" + } + ], + "name": "setMsgInspector", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_eid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "_peer", + "type": "bytes32" + } + ], + "name": "setPeer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_preCrime", + "type": "address" + } + ], + "name": "setPreCrime", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "sharedDecimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "transactionHash": "0xea36dd5b54286c6feab718066c95f219d4ba92b0b3081de858e576973e8d5af1", + "receipt": { + "to": null, + "from": "0x8583894d0e57e42abb83039537f314490038efa0", + "contractAddress": "0x2DC5e5177a172c0FDc7c7d490A5D6D098e822eB7", + "transactionIndex": 15, + "gasUsed": "2887200", + "logsBloom": "0x00000000000000000000000000000000000000000000000000800000000000000000000000000000220000000000000000000002000000000000000000000000000000000000000000000000000000000001004000000000000000000000400000000100020000000200000000000800000000000000000000000000000000400000000000000020000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf1ac07649113c05dc146ec763f36d8b29504d94f35e1009dcc81fb76a45f0054", + "transactionHash": "0xea36dd5b54286c6feab718066c95f219d4ba92b0b3081de858e576973e8d5af1", + "logs": [ + { + "transactionIndex": 15, + "blockNumber": 44875251, + "transactionHash": "0xea36dd5b54286c6feab718066c95f219d4ba92b0b3081de858e576973e8d5af1", + "address": "0x2DC5e5177a172c0FDc7c7d490A5D6D098e822eB7", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000008583894d0e57e42abb83039537f314490038efa0" + ], + "data": "0x", + "logIndex": 82, + "blockHash": "0xf1ac07649113c05dc146ec763f36d8b29504d94f35e1009dcc81fb76a45f0054" + }, + { + "transactionIndex": 15, + "blockNumber": 44875251, + "transactionHash": "0xea36dd5b54286c6feab718066c95f219d4ba92b0b3081de858e576973e8d5af1", + "address": "0x6EDCE65403992e310A62460808c4b910D972f10f", + "topics": [ + "0x6ee10e9ed4d6ce9742703a498707862f4b00f1396a87195eb93267b3d7983981" + ], + "data": "0x0000000000000000000000002dc5e5177a172c0fdc7c7d490a5d6d098e822eb70000000000000000000000008583894d0e57e42abb83039537f314490038efa0", + "logIndex": 83, + "blockHash": "0xf1ac07649113c05dc146ec763f36d8b29504d94f35e1009dcc81fb76a45f0054" + } + ], + "blockNumber": 44875251, + "cumulativeGasUsed": "4935230", + "status": 1, + "byzantium": true + }, + "args": [ + "testUSDT", + "testUSDT", + "0x6EDCE65403992e310A62460808c4b910D972f10f", + "0x8583894d0e57e42abb83039537f314490038efa0" + ], + "numDeployments": 2, + "solcInputHash": "8088d7b064191499b181ffda0ed40a97", + "metadata": "{\"compiler\":{\"version\":\"0.8.22+commit.4fc1097e\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_name\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"_symbol\",\"type\":\"string\"},{\"internalType\":\"address\",\"name\":\"_lzEndpoint\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_delegate\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amountSD\",\"type\":\"uint256\"}],\"name\":\"AmountSDOverflowed\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"allowance\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"needed\",\"type\":\"uint256\"}],\"name\":\"ERC20InsufficientAllowance\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"needed\",\"type\":\"uint256\"}],\"name\":\"ERC20InsufficientBalance\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"approver\",\"type\":\"address\"}],\"name\":\"ERC20InvalidApprover\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"}],\"name\":\"ERC20InvalidReceiver\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"ERC20InvalidSender\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"ERC20InvalidSpender\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidDelegate\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidEndpointCall\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidLocalDecimals\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"options\",\"type\":\"bytes\"}],\"name\":\"InvalidOptions\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"LzTokenUnavailable\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"}],\"name\":\"NoPeer\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"msgValue\",\"type\":\"uint256\"}],\"name\":\"NotEnoughNative\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"OnlyEndpoint\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"}],\"name\":\"OnlyPeer\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlySelf\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"OwnableInvalidOwner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"OwnableUnauthorizedAccount\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"}],\"name\":\"SafeERC20FailedOperation\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"result\",\"type\":\"bytes\"}],\"name\":\"SimulationResult\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amountLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minAmountLD\",\"type\":\"uint256\"}],\"name\":\"SlippageExceeded\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"msgType\",\"type\":\"uint16\"},{\"internalType\":\"bytes\",\"name\":\"options\",\"type\":\"bytes\"}],\"indexed\":false,\"internalType\":\"struct EnforcedOptionParam[]\",\"name\":\"_enforcedOptions\",\"type\":\"tuple[]\"}],\"name\":\"EnforcedOptionSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"inspector\",\"type\":\"address\"}],\"name\":\"MsgInspectorSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"guid\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"srcEid\",\"type\":\"uint32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"toAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amountReceivedLD\",\"type\":\"uint256\"}],\"name\":\"OFTReceived\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"guid\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"fromAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amountSentLD\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amountReceivedLD\",\"type\":\"uint256\"}],\"name\":\"OFTSent\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"peer\",\"type\":\"bytes32\"}],\"name\":\"PeerSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"preCrimeAddress\",\"type\":\"address\"}],\"name\":\"PreCrimeSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"SEND\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"\",\"type\":\"uint16\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"SEND_AND_CALL\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"\",\"type\":\"uint16\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"srcEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"struct Origin\",\"name\":\"origin\",\"type\":\"tuple\"}],\"name\":\"allowInitializePath\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"approvalRequired\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"_eid\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"_msgType\",\"type\":\"uint16\"},{\"internalType\":\"bytes\",\"name\":\"_extraOptions\",\"type\":\"bytes\"}],\"name\":\"combineOptions\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimalConversionRate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"endpoint\",\"outputs\":[{\"internalType\":\"contract ILayerZeroEndpointV2\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"msgType\",\"type\":\"uint16\"}],\"name\":\"enforcedOptions\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"enforcedOption\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"srcEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"struct Origin\",\"name\":\"\",\"type\":\"tuple\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"_sender\",\"type\":\"address\"}],\"name\":\"isComposeMsgSender\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"_eid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"_peer\",\"type\":\"bytes32\"}],\"name\":\"isPeer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"srcEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"struct Origin\",\"name\":\"_origin\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"_guid\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"_message\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"_executor\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_extraData\",\"type\":\"bytes\"}],\"name\":\"lzReceive\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"srcEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"struct Origin\",\"name\":\"origin\",\"type\":\"tuple\"},{\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"guid\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"executor\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"message\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"extraData\",\"type\":\"bytes\"}],\"internalType\":\"struct InboundPacket[]\",\"name\":\"_packets\",\"type\":\"tuple[]\"}],\"name\":\"lzReceiveAndRevert\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"srcEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"struct Origin\",\"name\":\"_origin\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"_guid\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"_message\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"_executor\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_extraData\",\"type\":\"bytes\"}],\"name\":\"lzReceiveSimulate\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"mint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"msgInspector\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"nextNonce\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"oApp\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"oAppVersion\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"senderVersion\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"receiverVersion\",\"type\":\"uint64\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"oftVersion\",\"outputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"},{\"internalType\":\"uint64\",\"name\":\"version\",\"type\":\"uint64\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"}],\"name\":\"peers\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"peer\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"preCrime\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"to\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"amountLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minAmountLD\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"extraOptions\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"composeMsg\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"oftCmd\",\"type\":\"bytes\"}],\"internalType\":\"struct SendParam\",\"name\":\"_sendParam\",\"type\":\"tuple\"}],\"name\":\"quoteOFT\",\"outputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"minAmountLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxAmountLD\",\"type\":\"uint256\"}],\"internalType\":\"struct OFTLimit\",\"name\":\"oftLimit\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"int256\",\"name\":\"feeAmountLD\",\"type\":\"int256\"},{\"internalType\":\"string\",\"name\":\"description\",\"type\":\"string\"}],\"internalType\":\"struct OFTFeeDetail[]\",\"name\":\"oftFeeDetails\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"amountSentLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountReceivedLD\",\"type\":\"uint256\"}],\"internalType\":\"struct OFTReceipt\",\"name\":\"oftReceipt\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"to\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"amountLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minAmountLD\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"extraOptions\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"composeMsg\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"oftCmd\",\"type\":\"bytes\"}],\"internalType\":\"struct SendParam\",\"name\":\"_sendParam\",\"type\":\"tuple\"},{\"internalType\":\"bool\",\"name\":\"_payInLzToken\",\"type\":\"bool\"}],\"name\":\"quoteSend\",\"outputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"nativeFee\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"lzTokenFee\",\"type\":\"uint256\"}],\"internalType\":\"struct MessagingFee\",\"name\":\"msgFee\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"to\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"amountLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minAmountLD\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"extraOptions\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"composeMsg\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"oftCmd\",\"type\":\"bytes\"}],\"internalType\":\"struct SendParam\",\"name\":\"_sendParam\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"nativeFee\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"lzTokenFee\",\"type\":\"uint256\"}],\"internalType\":\"struct MessagingFee\",\"name\":\"_fee\",\"type\":\"tuple\"},{\"internalType\":\"address\",\"name\":\"_refundAddress\",\"type\":\"address\"}],\"name\":\"send\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"guid\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"nativeFee\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"lzTokenFee\",\"type\":\"uint256\"}],\"internalType\":\"struct MessagingFee\",\"name\":\"fee\",\"type\":\"tuple\"}],\"internalType\":\"struct MessagingReceipt\",\"name\":\"msgReceipt\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"amountSentLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountReceivedLD\",\"type\":\"uint256\"}],\"internalType\":\"struct OFTReceipt\",\"name\":\"oftReceipt\",\"type\":\"tuple\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_delegate\",\"type\":\"address\"}],\"name\":\"setDelegate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"msgType\",\"type\":\"uint16\"},{\"internalType\":\"bytes\",\"name\":\"options\",\"type\":\"bytes\"}],\"internalType\":\"struct EnforcedOptionParam[]\",\"name\":\"_enforcedOptions\",\"type\":\"tuple[]\"}],\"name\":\"setEnforcedOptions\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_msgInspector\",\"type\":\"address\"}],\"name\":\"setMsgInspector\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"_eid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"_peer\",\"type\":\"bytes32\"}],\"name\":\"setPeer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_preCrime\",\"type\":\"address\"}],\"name\":\"setPreCrime\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"sharedDecimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"errors\":{\"ERC20InsufficientAllowance(address,uint256,uint256)\":[{\"details\":\"Indicates a failure with the `spender`\\u2019s `allowance`. Used in transfers.\",\"params\":{\"allowance\":\"Amount of tokens a `spender` is allowed to operate with.\",\"needed\":\"Minimum amount required to perform a transfer.\",\"spender\":\"Address that may be allowed to operate on tokens without being their owner.\"}}],\"ERC20InsufficientBalance(address,uint256,uint256)\":[{\"details\":\"Indicates an error related to the current `balance` of a `sender`. Used in transfers.\",\"params\":{\"balance\":\"Current balance for the interacting account.\",\"needed\":\"Minimum amount required to perform a transfer.\",\"sender\":\"Address whose tokens are being transferred.\"}}],\"ERC20InvalidApprover(address)\":[{\"details\":\"Indicates a failure with the `approver` of a token to be approved. Used in approvals.\",\"params\":{\"approver\":\"Address initiating an approval operation.\"}}],\"ERC20InvalidReceiver(address)\":[{\"details\":\"Indicates a failure with the token `receiver`. Used in transfers.\",\"params\":{\"receiver\":\"Address to which tokens are being transferred.\"}}],\"ERC20InvalidSender(address)\":[{\"details\":\"Indicates a failure with the token `sender`. Used in transfers.\",\"params\":{\"sender\":\"Address whose tokens are being transferred.\"}}],\"ERC20InvalidSpender(address)\":[{\"details\":\"Indicates a failure with the `spender` to be approved. Used in approvals.\",\"params\":{\"spender\":\"Address that may be allowed to operate on tokens without being their owner.\"}}],\"OwnableInvalidOwner(address)\":[{\"details\":\"The owner is not a valid owner account. (eg. `address(0)`)\"}],\"OwnableUnauthorizedAccount(address)\":[{\"details\":\"The caller account is not authorized to perform an operation.\"}],\"SafeERC20FailedOperation(address)\":[{\"details\":\"An operation with an ERC-20 token failed.\"}]},\"events\":{\"Approval(address,address,uint256)\":{\"details\":\"Emitted when the allowance of a `spender` for an `owner` is set by a call to {approve}. `value` is the new allowance.\"},\"PreCrimeSet(address)\":{\"details\":\"Emitted when the preCrime contract address is set.\",\"params\":{\"preCrimeAddress\":\"The address of the preCrime contract.\"}},\"Transfer(address,address,uint256)\":{\"details\":\"Emitted when `value` tokens are moved from one account (`from`) to another (`to`). Note that `value` may be zero.\"}},\"kind\":\"dev\",\"methods\":{\"allowInitializePath((uint32,bytes32,uint64))\":{\"details\":\"This indicates to the endpoint that the OApp has enabled msgs for this particular path to be received.This defaults to assuming if a peer has been set, its initialized. Can be overridden by the OApp if there is other logic to determine this.\",\"params\":{\"origin\":\"The origin information containing the source endpoint and sender address.\"},\"returns\":{\"_0\":\"Whether the path has been initialized.\"}},\"allowance(address,address)\":{\"details\":\"Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` through {transferFrom}. This is zero by default. This value changes when {approve} or {transferFrom} are called.\"},\"approvalRequired()\":{\"details\":\"In the case of OFT where the contract IS the token, approval is NOT required.\",\"returns\":{\"_0\":\"requiresApproval Needs approval of the underlying token implementation.\"}},\"approve(address,uint256)\":{\"details\":\"See {IERC20-approve}. NOTE: If `value` is the maximum `uint256`, the allowance is not updated on `transferFrom`. This is semantically equivalent to an infinite approval. Requirements: - `spender` cannot be the zero address.\"},\"balanceOf(address)\":{\"details\":\"Returns the value of tokens owned by `account`.\"},\"combineOptions(uint32,uint16,bytes)\":{\"details\":\"If there is an enforced lzReceive option: - {gasLimit: 200k, msg.value: 1 ether} AND a caller supplies a lzReceive option: {gasLimit: 100k, msg.value: 0.5 ether} - The resulting options will be {gasLimit: 300k, msg.value: 1.5 ether} when the message is executed on the remote lzReceive() function.This presence of duplicated options is handled off-chain in the verifier/executor.\",\"params\":{\"_eid\":\"The endpoint ID.\",\"_extraOptions\":\"Additional options passed by the caller.\",\"_msgType\":\"The OAPP message type.\"},\"returns\":{\"_0\":\"options The combination of caller specified options AND enforced options.\"}},\"decimals()\":{\"details\":\"Returns the number of decimals used to get its user representation. For example, if `decimals` equals `2`, a balance of `505` tokens should be displayed to a user as `5.05` (`505 / 10 ** 2`). Tokens usually opt for a value of 18, imitating the relationship between Ether and Wei. This is the default value returned by this function, unless it's overridden. NOTE: This information is only used for _display_ purposes: it in no way affects any of the arithmetic of the contract, including {IERC20-balanceOf} and {IERC20-transfer}.\"},\"isComposeMsgSender((uint32,bytes32,uint64),bytes,address)\":{\"details\":\"_origin The origin information containing the source endpoint and sender address. - srcEid: The source chain endpoint ID. - sender: The sender address on the src chain. - nonce: The nonce of the message._message The lzReceive payload.Applications can optionally choose to implement separate composeMsg senders that are NOT the bridging layer.The default sender IS the OAppReceiver implementer.\",\"params\":{\"_sender\":\"The sender address.\"},\"returns\":{\"_0\":\"isSender Is a valid sender.\"}},\"isPeer(uint32,bytes32)\":{\"details\":\"Check if the peer is considered 'trusted' by the OApp.Enables OAppPreCrimeSimulator to check whether a potential Inbound Packet is from a trusted source.\",\"params\":{\"_eid\":\"The endpoint ID to check.\",\"_peer\":\"The peer to check.\"},\"returns\":{\"_0\":\"Whether the peer passed is considered 'trusted' by the OApp.\"}},\"lzReceive((uint32,bytes32,uint64),bytes32,bytes,address,bytes)\":{\"details\":\"Entry point for receiving messages or packets from the endpoint.Entry point for receiving msg/packet from the LayerZero endpoint.\",\"params\":{\"_executor\":\"The address of the executor for the received message.\",\"_extraData\":\"Additional arbitrary data provided by the corresponding executor.\",\"_guid\":\"The unique identifier for the received LayerZero message.\",\"_message\":\"The payload of the received message.\",\"_origin\":\"The origin information containing the source endpoint and sender address. - srcEid: The source chain endpoint ID. - sender: The sender address on the src chain. - nonce: The nonce of the message.\"}},\"lzReceiveAndRevert(((uint32,bytes32,uint64),uint32,address,bytes32,uint256,address,bytes,bytes)[])\":{\"details\":\"Interface for pre-crime simulations. Always reverts at the end with the simulation results.WARNING: MUST revert at the end with the simulation results.Gives the preCrime implementation the ability to mock sending packets to the lzReceive function, WITHOUT actually executing them.\",\"params\":{\"_packets\":\"An array of InboundPacket objects representing received packets to be delivered.\"}},\"lzReceiveSimulate((uint32,bytes32,uint64),bytes32,bytes,address,bytes)\":{\"details\":\"Is effectively an internal function because msg.sender must be address(this). Allows resetting the call stack for 'internal' calls.\",\"params\":{\"_executor\":\"The executor address for the packet.\",\"_extraData\":\"Additional data for the packet.\",\"_guid\":\"The unique identifier of the packet.\",\"_message\":\"The message payload of the packet.\",\"_origin\":\"The origin information containing the source endpoint and sender address. - srcEid: The source chain endpoint ID. - sender: The sender address on the src chain. - nonce: The nonce of the message.\"}},\"mint(address,uint256)\":{\"details\":\"Deliberately unguarded: the demo mints to itself before each send, and a testnet OFT with no way to obtain tokens cannot be used to exercise the DVN at all. Do NOT ship this to a network where the token has value. `virtual` because MyOFTMock declares the same function for the hardhat tests.\"},\"name()\":{\"details\":\"Returns the name of the token.\"},\"nextNonce(uint32,bytes32)\":{\"details\":\"_srcEid The source endpoint ID._sender The sender address.The path nonce starts from 1. If 0 is returned it means that there is NO nonce ordered enforcement.Is required by the off-chain executor to determine the OApp expects msg execution is ordered.This is also enforced by the OApp.By default this is NOT enabled. ie. nextNonce is hardcoded to return 0.\",\"returns\":{\"nonce\":\"The next nonce.\"}},\"oApp()\":{\"details\":\"Retrieves the address of the OApp contract.The simulator contract is the base contract for the OApp by default.If the simulator is a separate contract, override this function.\",\"returns\":{\"_0\":\"The address of the OApp contract.\"}},\"oAppVersion()\":{\"returns\":{\"receiverVersion\":\"The version of the OAppReceiver.sol implementation.\",\"senderVersion\":\"The version of the OAppSender.sol implementation.\"}},\"oftVersion()\":{\"details\":\"interfaceId: This specific interface ID is '0x02e49c2c'.version: Indicates a cross-chain compatible msg encoding with other OFTs.If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented. ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)\",\"returns\":{\"interfaceId\":\"The interface ID.\",\"version\":\"The version.\"}},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"quoteOFT((uint32,bytes32,uint256,uint256,bytes,bytes,bytes))\":{\"params\":{\"_sendParam\":\"The parameters for the send operation.\"},\"returns\":{\"oftFeeDetails\":\"The details of OFT fees.\",\"oftLimit\":\"The OFT limit information.\",\"oftReceipt\":\"The OFT receipt information.\"}},\"quoteSend((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),bool)\":{\"details\":\"MessagingFee: LayerZero msg fee - nativeFee: The native fee. - lzTokenFee: The lzToken fee.\",\"params\":{\"_payInLzToken\":\"Flag indicating whether the caller is paying in the LZ token.\",\"_sendParam\":\"The parameters for the send() operation.\"},\"returns\":{\"msgFee\":\"The calculated LayerZero messaging fee from the send() operation.\"}},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner.\"},\"send((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),(uint256,uint256),address)\":{\"details\":\"Executes the send operation.MessagingReceipt: LayerZero msg receipt - guid: The unique identifier for the sent message. - nonce: The nonce of the sent message. - fee: The LayerZero fee incurred for the message.\",\"params\":{\"_fee\":\"The calculated fee for the send() operation. - nativeFee: The native fee. - lzTokenFee: The lzToken fee.\",\"_refundAddress\":\"The address to receive any excess funds.\",\"_sendParam\":\"The parameters for the send operation.\"},\"returns\":{\"msgReceipt\":\"The receipt for the send operation.\",\"oftReceipt\":\"The OFT receipt information.\"}},\"setDelegate(address)\":{\"details\":\"Only the owner/admin of the OApp can call this function.Provides the ability for a delegate to set configs, on behalf of the OApp, directly on the Endpoint contract.\",\"params\":{\"_delegate\":\"The address of the delegate to be set.\"}},\"setEnforcedOptions((uint32,uint16,bytes)[])\":{\"details\":\"Sets the enforced options for specific endpoint and message type combinations.Only the owner/admin of the OApp can call this function.Provides a way for the OApp to enforce things like paying for PreCrime, AND/OR minimum dst lzReceive gas amounts etc.These enforced options can vary as the potential options/execution on the remote may differ as per the msgType. eg. Amount of lzReceive() gas necessary to deliver a lzCompose() message adds overhead you dont want to pay if you are only making a standard LayerZero message ie. lzReceive() WITHOUT sendCompose().\",\"params\":{\"_enforcedOptions\":\"An array of EnforcedOptionParam structures specifying enforced options.\"}},\"setMsgInspector(address)\":{\"details\":\"Sets the message inspector address for the OFT.This is an optional contract that can be used to inspect both 'message' and 'options'.Set it to address(0) to disable it, or set it to a contract address to enable it.\",\"params\":{\"_msgInspector\":\"The address of the message inspector.\"}},\"setPeer(uint32,bytes32)\":{\"details\":\"Only the owner/admin of the OApp can call this function.Indicates that the peer is trusted to send LayerZero messages to this OApp.Set this to bytes32(0) to remove the peer address.Peer is a bytes32 to accommodate non-evm chains.\",\"params\":{\"_eid\":\"The endpoint ID.\",\"_peer\":\"The address of the peer to be associated with the corresponding endpoint.\"}},\"setPreCrime(address)\":{\"details\":\"Sets the preCrime contract address.\",\"params\":{\"_preCrime\":\"The address of the preCrime contract.\"}},\"sharedDecimals()\":{\"details\":\"Retrieves the shared decimals of the OFT.Sets an implicit cap on the amount of tokens, over uint64.max() will need some sort of outbound cap / totalSupply cap Lowest common decimal denominator between chains. Defaults to 6 decimal places to provide up to 18,446,744,073,709.551615 units (max uint64). For tokens exceeding this totalSupply(), they will need to override the sharedDecimals function with something smaller. ie. 4 sharedDecimals would be 1,844,674,407,370,955.1615\",\"returns\":{\"_0\":\"The shared decimals of the OFT.\"}},\"symbol()\":{\"details\":\"Returns the symbol of the token, usually a shorter version of the name.\"},\"token()\":{\"details\":\"Retrieves the address of the underlying ERC20 implementation.In the case of OFT, address(this) and erc20 are the same contract.\",\"returns\":{\"_0\":\"The address of the OFT token.\"}},\"totalSupply()\":{\"details\":\"Returns the value of tokens in existence.\"},\"transfer(address,uint256)\":{\"details\":\"See {IERC20-transfer}. Requirements: - `to` cannot be the zero address. - the caller must have a balance of at least `value`.\"},\"transferFrom(address,address,uint256)\":{\"details\":\"See {IERC20-transferFrom}. Skips emitting an {Approval} event indicating an allowance update. This is not required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve]. NOTE: Does not update the allowance if the current allowance is the maximum `uint256`. Requirements: - `from` and `to` cannot be the zero address. - `from` must have a balance of at least `value`. - the caller must have allowance for ``from``'s tokens of at least `value`.\"},\"transferOwnership(address)\":{\"details\":\"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"allowInitializePath((uint32,bytes32,uint64))\":{\"notice\":\"Checks if the path initialization is allowed based on the provided origin.\"},\"approvalRequired()\":{\"notice\":\"Indicates whether the OFT contract requires approval of the 'token()' to send.\"},\"combineOptions(uint32,uint16,bytes)\":{\"notice\":\"Combines options for a given endpoint and message type.\"},\"endpoint()\":{\"notice\":\"Retrieves the LayerZero endpoint associated with the OApp.\"},\"isComposeMsgSender((uint32,bytes32,uint64),bytes,address)\":{\"notice\":\"Indicates whether an address is an approved composeMsg sender to the Endpoint.\"},\"mint(address,uint256)\":{\"notice\":\"Open mint, TESTNET ONLY \\u2014 same as ToyOFT, so the demo tasks work against either.\"},\"nextNonce(uint32,bytes32)\":{\"notice\":\"Retrieves the next nonce for a given source endpoint and sender address.\"},\"oAppVersion()\":{\"notice\":\"Retrieves the OApp version information.\"},\"oftVersion()\":{\"notice\":\"Retrieves interfaceID and the version of the OFT.\"},\"peers(uint32)\":{\"notice\":\"Retrieves the peer (OApp) associated with a corresponding endpoint.\"},\"quoteOFT((uint32,bytes32,uint256,uint256,bytes,bytes,bytes))\":{\"notice\":\"Provides the fee breakdown and settings data for an OFT. Unused in the default implementation.\"},\"quoteSend((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),bool)\":{\"notice\":\"Provides a quote for the send() operation.\"},\"setDelegate(address)\":{\"notice\":\"Sets the delegate address for the OApp.\"},\"setPeer(uint32,bytes32)\":{\"notice\":\"Sets the peer address (OApp instance) for a corresponding endpoint.\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/MyOFT.sol\":\"MyOFT\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\nimport { IMessageLibManager } from \\\"./IMessageLibManager.sol\\\";\\nimport { IMessagingComposer } from \\\"./IMessagingComposer.sol\\\";\\nimport { IMessagingChannel } from \\\"./IMessagingChannel.sol\\\";\\nimport { IMessagingContext } from \\\"./IMessagingContext.sol\\\";\\n\\nstruct MessagingParams {\\n uint32 dstEid;\\n bytes32 receiver;\\n bytes message;\\n bytes options;\\n bool payInLzToken;\\n}\\n\\nstruct MessagingReceipt {\\n bytes32 guid;\\n uint64 nonce;\\n MessagingFee fee;\\n}\\n\\nstruct MessagingFee {\\n uint256 nativeFee;\\n uint256 lzTokenFee;\\n}\\n\\nstruct Origin {\\n uint32 srcEid;\\n bytes32 sender;\\n uint64 nonce;\\n}\\n\\ninterface ILayerZeroEndpointV2 is IMessageLibManager, IMessagingComposer, IMessagingChannel, IMessagingContext {\\n event PacketSent(bytes encodedPayload, bytes options, address sendLibrary);\\n\\n event PacketVerified(Origin origin, address receiver, bytes32 payloadHash);\\n\\n event PacketDelivered(Origin origin, address receiver);\\n\\n event LzReceiveAlert(\\n address indexed receiver,\\n address indexed executor,\\n Origin origin,\\n bytes32 guid,\\n uint256 gas,\\n uint256 value,\\n bytes message,\\n bytes extraData,\\n bytes reason\\n );\\n\\n event LzTokenSet(address token);\\n\\n event DelegateSet(address sender, address delegate);\\n\\n function quote(MessagingParams calldata _params, address _sender) external view returns (MessagingFee memory);\\n\\n function send(\\n MessagingParams calldata _params,\\n address _refundAddress\\n ) external payable returns (MessagingReceipt memory);\\n\\n function verify(Origin calldata _origin, address _receiver, bytes32 _payloadHash) external;\\n\\n function verifiable(Origin calldata _origin, address _receiver) external view returns (bool);\\n\\n function initializable(Origin calldata _origin, address _receiver) external view returns (bool);\\n\\n function lzReceive(\\n Origin calldata _origin,\\n address _receiver,\\n bytes32 _guid,\\n bytes calldata _message,\\n bytes calldata _extraData\\n ) external payable;\\n\\n // oapp can burn messages partially by calling this function with its own business logic if messages are verified in order\\n function clear(address _oapp, Origin calldata _origin, bytes32 _guid, bytes calldata _message) external;\\n\\n function setLzToken(address _lzToken) external;\\n\\n function lzToken() external view returns (address);\\n\\n function nativeToken() external view returns (address);\\n\\n function setDelegate(address _delegate) external;\\n}\\n\",\"keccak256\":\"0xf7f941bee89ea6369950fe54e8ac476ae6478b958b20fc0e8a83e8ff1364eac3\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroReceiver.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\nimport { Origin } from \\\"./ILayerZeroEndpointV2.sol\\\";\\n\\ninterface ILayerZeroReceiver {\\n function allowInitializePath(Origin calldata _origin) external view returns (bool);\\n\\n function nextNonce(uint32 _eid, bytes32 _sender) external view returns (uint64);\\n\\n function lzReceive(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address _executor,\\n bytes calldata _extraData\\n ) external payable;\\n}\\n\",\"keccak256\":\"0x9641abba8d53b08bb517d1b74801dd15ea7b84d77a6719085bd96c8ea94e3ca0\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLib.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\nimport { IERC165 } from \\\"@openzeppelin/contracts/utils/introspection/IERC165.sol\\\";\\n\\nimport { SetConfigParam } from \\\"./IMessageLibManager.sol\\\";\\n\\nenum MessageLibType {\\n Send,\\n Receive,\\n SendAndReceive\\n}\\n\\ninterface IMessageLib is IERC165 {\\n function setConfig(address _oapp, SetConfigParam[] calldata _config) external;\\n\\n function getConfig(uint32 _eid, address _oapp, uint32 _configType) external view returns (bytes memory config);\\n\\n function isSupportedEid(uint32 _eid) external view returns (bool);\\n\\n // message libs of same major version are compatible\\n function version() external view returns (uint64 major, uint8 minor, uint8 endpointVersion);\\n\\n function messageLibType() external view returns (MessageLibType);\\n}\\n\",\"keccak256\":\"0x5cf5f24751b4e3ea1c9c5ded07cedfdfd62566b6daaffcc0144733859c9dba0c\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLibManager.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\nstruct SetConfigParam {\\n uint32 eid;\\n uint32 configType;\\n bytes config;\\n}\\n\\ninterface IMessageLibManager {\\n struct Timeout {\\n address lib;\\n uint256 expiry;\\n }\\n\\n event LibraryRegistered(address newLib);\\n event DefaultSendLibrarySet(uint32 eid, address newLib);\\n event DefaultReceiveLibrarySet(uint32 eid, address newLib);\\n event DefaultReceiveLibraryTimeoutSet(uint32 eid, address oldLib, uint256 expiry);\\n event SendLibrarySet(address sender, uint32 eid, address newLib);\\n event ReceiveLibrarySet(address receiver, uint32 eid, address newLib);\\n event ReceiveLibraryTimeoutSet(address receiver, uint32 eid, address oldLib, uint256 timeout);\\n\\n function registerLibrary(address _lib) external;\\n\\n function isRegisteredLibrary(address _lib) external view returns (bool);\\n\\n function getRegisteredLibraries() external view returns (address[] memory);\\n\\n function setDefaultSendLibrary(uint32 _eid, address _newLib) external;\\n\\n function defaultSendLibrary(uint32 _eid) external view returns (address);\\n\\n function setDefaultReceiveLibrary(uint32 _eid, address _newLib, uint256 _gracePeriod) external;\\n\\n function defaultReceiveLibrary(uint32 _eid) external view returns (address);\\n\\n function setDefaultReceiveLibraryTimeout(uint32 _eid, address _lib, uint256 _expiry) external;\\n\\n function defaultReceiveLibraryTimeout(uint32 _eid) external view returns (address lib, uint256 expiry);\\n\\n function isSupportedEid(uint32 _eid) external view returns (bool);\\n\\n function isValidReceiveLibrary(address _receiver, uint32 _eid, address _lib) external view returns (bool);\\n\\n /// ------------------- OApp interfaces -------------------\\n function setSendLibrary(address _oapp, uint32 _eid, address _newLib) external;\\n\\n function getSendLibrary(address _sender, uint32 _eid) external view returns (address lib);\\n\\n function isDefaultSendLibrary(address _sender, uint32 _eid) external view returns (bool);\\n\\n function setReceiveLibrary(address _oapp, uint32 _eid, address _newLib, uint256 _gracePeriod) external;\\n\\n function getReceiveLibrary(address _receiver, uint32 _eid) external view returns (address lib, bool isDefault);\\n\\n function setReceiveLibraryTimeout(address _oapp, uint32 _eid, address _lib, uint256 _expiry) external;\\n\\n function receiveLibraryTimeout(address _receiver, uint32 _eid) external view returns (address lib, uint256 expiry);\\n\\n function setConfig(address _oapp, address _lib, SetConfigParam[] calldata _params) external;\\n\\n function getConfig(\\n address _oapp,\\n address _lib,\\n uint32 _eid,\\n uint32 _configType\\n ) external view returns (bytes memory config);\\n}\\n\",\"keccak256\":\"0x919b37133adff4dc528e3061deb2789c3149971b530c61e556fb3d09ab315dfc\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingChannel.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\ninterface IMessagingChannel {\\n event InboundNonceSkipped(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce);\\n event PacketNilified(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce, bytes32 payloadHash);\\n event PacketBurnt(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce, bytes32 payloadHash);\\n\\n function eid() external view returns (uint32);\\n\\n // this is an emergency function if a message cannot be verified for some reasons\\n // required to provide _nextNonce to avoid race condition\\n function skip(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce) external;\\n\\n function nilify(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes32 _payloadHash) external;\\n\\n function burn(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes32 _payloadHash) external;\\n\\n function nextGuid(address _sender, uint32 _dstEid, bytes32 _receiver) external view returns (bytes32);\\n\\n function inboundNonce(address _receiver, uint32 _srcEid, bytes32 _sender) external view returns (uint64);\\n\\n function outboundNonce(address _sender, uint32 _dstEid, bytes32 _receiver) external view returns (uint64);\\n\\n function inboundPayloadHash(\\n address _receiver,\\n uint32 _srcEid,\\n bytes32 _sender,\\n uint64 _nonce\\n ) external view returns (bytes32);\\n\\n function lazyInboundNonce(address _receiver, uint32 _srcEid, bytes32 _sender) external view returns (uint64);\\n}\\n\",\"keccak256\":\"0x0878f64dffebf58c4165569416372f40860fab546b88cd926eba0d5cb6d8d972\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingComposer.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\ninterface IMessagingComposer {\\n event ComposeSent(address from, address to, bytes32 guid, uint16 index, bytes message);\\n event ComposeDelivered(address from, address to, bytes32 guid, uint16 index);\\n event LzComposeAlert(\\n address indexed from,\\n address indexed to,\\n address indexed executor,\\n bytes32 guid,\\n uint16 index,\\n uint256 gas,\\n uint256 value,\\n bytes message,\\n bytes extraData,\\n bytes reason\\n );\\n\\n function composeQueue(\\n address _from,\\n address _to,\\n bytes32 _guid,\\n uint16 _index\\n ) external view returns (bytes32 messageHash);\\n\\n function sendCompose(address _to, bytes32 _guid, uint16 _index, bytes calldata _message) external;\\n\\n function lzCompose(\\n address _from,\\n address _to,\\n bytes32 _guid,\\n uint16 _index,\\n bytes calldata _message,\\n bytes calldata _extraData\\n ) external payable;\\n}\\n\",\"keccak256\":\"0x85bc7090134529ec474866dc4bb1c48692d518c756eb0a961c82574829c51901\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingContext.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\ninterface IMessagingContext {\\n function isSendingMessage() external view returns (bool);\\n\\n function getSendContext() external view returns (uint32 dstEid, address sender);\\n}\\n\",\"keccak256\":\"0xff0c546c2813dae3e440882f46b377375f7461b0714efd80bd3f0c6e5cb8da4e\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ISendLib.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\nimport { MessagingFee } from \\\"./ILayerZeroEndpointV2.sol\\\";\\nimport { IMessageLib } from \\\"./IMessageLib.sol\\\";\\n\\nstruct Packet {\\n uint64 nonce;\\n uint32 srcEid;\\n address sender;\\n uint32 dstEid;\\n bytes32 receiver;\\n bytes32 guid;\\n bytes message;\\n}\\n\\ninterface ISendLib is IMessageLib {\\n function send(\\n Packet calldata _packet,\\n bytes calldata _options,\\n bool _payInLzToken\\n ) external returns (MessagingFee memory, bytes memory encodedPacket);\\n\\n function quote(\\n Packet calldata _packet,\\n bytes calldata _options,\\n bool _payInLzToken\\n ) external view returns (MessagingFee memory);\\n\\n function setTreasury(address _treasury) external;\\n\\n function withdrawFee(address _to, uint256 _amount) external;\\n\\n function withdrawLzTokenFee(address _lzToken, address _to, uint256 _amount) external;\\n}\\n\",\"keccak256\":\"0xf1c07bc61e7b1dce195ed12d50f87980fbf2d63cac1326fd28287f55fe0ba625\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/libs/AddressCast.sol\":{\"content\":\"// SPDX-License-Identifier: LZBL-1.2\\n\\npragma solidity ^0.8.20;\\n\\nlibrary AddressCast {\\n error AddressCast_InvalidSizeForAddress();\\n error AddressCast_InvalidAddress();\\n\\n function toBytes32(bytes calldata _addressBytes) internal pure returns (bytes32 result) {\\n if (_addressBytes.length > 32) revert AddressCast_InvalidAddress();\\n result = bytes32(_addressBytes);\\n unchecked {\\n uint256 offset = 32 - _addressBytes.length;\\n result = result >> (offset * 8);\\n }\\n }\\n\\n function toBytes32(address _address) internal pure returns (bytes32 result) {\\n result = bytes32(uint256(uint160(_address)));\\n }\\n\\n function toBytes(bytes32 _addressBytes32, uint256 _size) internal pure returns (bytes memory result) {\\n if (_size == 0 || _size > 32) revert AddressCast_InvalidSizeForAddress();\\n result = new bytes(_size);\\n unchecked {\\n uint256 offset = 256 - _size * 8;\\n assembly {\\n mstore(add(result, 32), shl(offset, _addressBytes32))\\n }\\n }\\n }\\n\\n function toAddress(bytes32 _addressBytes32) internal pure returns (address result) {\\n result = address(uint160(uint256(_addressBytes32)));\\n }\\n\\n function toAddress(bytes calldata _addressBytes) internal pure returns (address result) {\\n if (_addressBytes.length != 20) revert AddressCast_InvalidAddress();\\n result = address(bytes20(_addressBytes));\\n }\\n}\\n\",\"keccak256\":\"0x2ebbcaaab3554edcd41b581f1a72ac1806afbfb8047d0d47ff098f9af30d6deb\",\"license\":\"LZBL-1.2\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.sol\":{\"content\":\"// SPDX-License-Identifier: LZBL-1.2\\n\\npragma solidity ^0.8.20;\\n\\nimport { Packet } from \\\"../../interfaces/ISendLib.sol\\\";\\nimport { AddressCast } from \\\"../../libs/AddressCast.sol\\\";\\n\\nlibrary PacketV1Codec {\\n using AddressCast for address;\\n using AddressCast for bytes32;\\n\\n uint8 internal constant PACKET_VERSION = 1;\\n\\n // header (version + nonce + path)\\n // version\\n uint256 private constant PACKET_VERSION_OFFSET = 0;\\n // nonce\\n uint256 private constant NONCE_OFFSET = 1;\\n // path\\n uint256 private constant SRC_EID_OFFSET = 9;\\n uint256 private constant SENDER_OFFSET = 13;\\n uint256 private constant DST_EID_OFFSET = 45;\\n uint256 private constant RECEIVER_OFFSET = 49;\\n // payload (guid + message)\\n uint256 private constant GUID_OFFSET = 81; // keccak256(nonce + path)\\n uint256 private constant MESSAGE_OFFSET = 113;\\n\\n function encode(Packet memory _packet) internal pure returns (bytes memory encodedPacket) {\\n encodedPacket = abi.encodePacked(\\n PACKET_VERSION,\\n _packet.nonce,\\n _packet.srcEid,\\n _packet.sender.toBytes32(),\\n _packet.dstEid,\\n _packet.receiver,\\n _packet.guid,\\n _packet.message\\n );\\n }\\n\\n function encodePacketHeader(Packet memory _packet) internal pure returns (bytes memory) {\\n return\\n abi.encodePacked(\\n PACKET_VERSION,\\n _packet.nonce,\\n _packet.srcEid,\\n _packet.sender.toBytes32(),\\n _packet.dstEid,\\n _packet.receiver\\n );\\n }\\n\\n function encodePayload(Packet memory _packet) internal pure returns (bytes memory) {\\n return abi.encodePacked(_packet.guid, _packet.message);\\n }\\n\\n function header(bytes calldata _packet) internal pure returns (bytes calldata) {\\n return _packet[0:GUID_OFFSET];\\n }\\n\\n function version(bytes calldata _packet) internal pure returns (uint8) {\\n return uint8(bytes1(_packet[PACKET_VERSION_OFFSET:NONCE_OFFSET]));\\n }\\n\\n function nonce(bytes calldata _packet) internal pure returns (uint64) {\\n return uint64(bytes8(_packet[NONCE_OFFSET:SRC_EID_OFFSET]));\\n }\\n\\n function srcEid(bytes calldata _packet) internal pure returns (uint32) {\\n return uint32(bytes4(_packet[SRC_EID_OFFSET:SENDER_OFFSET]));\\n }\\n\\n function sender(bytes calldata _packet) internal pure returns (bytes32) {\\n return bytes32(_packet[SENDER_OFFSET:DST_EID_OFFSET]);\\n }\\n\\n function senderAddressB20(bytes calldata _packet) internal pure returns (address) {\\n return sender(_packet).toAddress();\\n }\\n\\n function dstEid(bytes calldata _packet) internal pure returns (uint32) {\\n return uint32(bytes4(_packet[DST_EID_OFFSET:RECEIVER_OFFSET]));\\n }\\n\\n function receiver(bytes calldata _packet) internal pure returns (bytes32) {\\n return bytes32(_packet[RECEIVER_OFFSET:GUID_OFFSET]);\\n }\\n\\n function receiverB20(bytes calldata _packet) internal pure returns (address) {\\n return receiver(_packet).toAddress();\\n }\\n\\n function guid(bytes calldata _packet) internal pure returns (bytes32) {\\n return bytes32(_packet[GUID_OFFSET:MESSAGE_OFFSET]);\\n }\\n\\n function message(bytes calldata _packet) internal pure returns (bytes calldata) {\\n return bytes(_packet[MESSAGE_OFFSET:]);\\n }\\n\\n function payload(bytes calldata _packet) internal pure returns (bytes calldata) {\\n return bytes(_packet[GUID_OFFSET:]);\\n }\\n\\n function payloadHash(bytes calldata _packet) internal pure returns (bytes32) {\\n return keccak256(payload(_packet));\\n }\\n}\\n\",\"keccak256\":\"0xc84cf1bf785977fe1fbe7566eef902c2db68d0e163813ebe6c34921754802680\",\"license\":\"LZBL-1.2\"},\"@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\n// @dev Import the 'MessagingFee' and 'MessagingReceipt' so it's exposed to OApp implementers\\n// solhint-disable-next-line no-unused-import\\nimport { OAppSender, MessagingFee, MessagingReceipt } from \\\"./OAppSender.sol\\\";\\n// @dev Import the 'Origin' so it's exposed to OApp implementers\\n// solhint-disable-next-line no-unused-import\\nimport { OAppReceiver, Origin } from \\\"./OAppReceiver.sol\\\";\\nimport { OAppCore } from \\\"./OAppCore.sol\\\";\\n\\n/**\\n * @title OApp\\n * @dev Abstract contract serving as the base for OApp implementation, combining OAppSender and OAppReceiver functionality.\\n */\\nabstract contract OApp is OAppSender, OAppReceiver {\\n /**\\n * @dev Constructor to initialize the OApp with the provided endpoint and owner.\\n * @param _endpoint The address of the LOCAL LayerZero endpoint.\\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\\n */\\n constructor(address _endpoint, address _delegate) OAppCore(_endpoint, _delegate) {}\\n\\n /**\\n * @notice Retrieves the OApp version information.\\n * @return senderVersion The version of the OAppSender.sol implementation.\\n * @return receiverVersion The version of the OAppReceiver.sol implementation.\\n */\\n function oAppVersion()\\n public\\n pure\\n virtual\\n override(OAppSender, OAppReceiver)\\n returns (uint64 senderVersion, uint64 receiverVersion)\\n {\\n return (SENDER_VERSION, RECEIVER_VERSION);\\n }\\n}\\n\",\"keccak256\":\"0xac362c4c291fad2f1511a968424b2e78a5ad502d1c867bd31da04be742aca8c5\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/OAppCore.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { Ownable } from \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\nimport { IOAppCore, ILayerZeroEndpointV2 } from \\\"./interfaces/IOAppCore.sol\\\";\\n\\n/**\\n * @title OAppCore\\n * @dev Abstract contract implementing the IOAppCore interface with basic OApp configurations.\\n */\\nabstract contract OAppCore is IOAppCore, Ownable {\\n // The LayerZero endpoint associated with the given OApp\\n ILayerZeroEndpointV2 public immutable endpoint;\\n\\n // Mapping to store peers associated with corresponding endpoints\\n mapping(uint32 eid => bytes32 peer) public peers;\\n\\n /**\\n * @dev Constructor to initialize the OAppCore with the provided endpoint and delegate.\\n * @param _endpoint The address of the LOCAL Layer Zero endpoint.\\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\\n *\\n * @dev The delegate typically should be set as the owner of the contract.\\n */\\n constructor(address _endpoint, address _delegate) {\\n endpoint = ILayerZeroEndpointV2(_endpoint);\\n\\n if (_delegate == address(0)) revert InvalidDelegate();\\n endpoint.setDelegate(_delegate);\\n }\\n\\n /**\\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\\n * @param _eid The endpoint ID.\\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\\n *\\n * @dev Only the owner/admin of the OApp can call this function.\\n * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp.\\n * @dev Set this to bytes32(0) to remove the peer address.\\n * @dev Peer is a bytes32 to accommodate non-evm chains.\\n */\\n function setPeer(uint32 _eid, bytes32 _peer) public virtual onlyOwner {\\n _setPeer(_eid, _peer);\\n }\\n\\n /**\\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\\n * @param _eid The endpoint ID.\\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\\n *\\n * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp.\\n * @dev Set this to bytes32(0) to remove the peer address.\\n * @dev Peer is a bytes32 to accommodate non-evm chains.\\n */\\n function _setPeer(uint32 _eid, bytes32 _peer) internal virtual {\\n peers[_eid] = _peer;\\n emit PeerSet(_eid, _peer);\\n }\\n\\n /**\\n * @notice Internal function to get the peer address associated with a specific endpoint; reverts if NOT set.\\n * ie. the peer is set to bytes32(0).\\n * @param _eid The endpoint ID.\\n * @return peer The address of the peer associated with the specified endpoint.\\n */\\n function _getPeerOrRevert(uint32 _eid) internal view virtual returns (bytes32) {\\n bytes32 peer = peers[_eid];\\n if (peer == bytes32(0)) revert NoPeer(_eid);\\n return peer;\\n }\\n\\n /**\\n * @notice Sets the delegate address for the OApp.\\n * @param _delegate The address of the delegate to be set.\\n *\\n * @dev Only the owner/admin of the OApp can call this function.\\n * @dev Provides the ability for a delegate to set configs, on behalf of the OApp, directly on the Endpoint contract.\\n */\\n function setDelegate(address _delegate) public onlyOwner {\\n endpoint.setDelegate(_delegate);\\n }\\n}\\n\",\"keccak256\":\"0x13a9c2d1d2c1f086b8624f2e84c4a4702212daae36f701d92bb915b535cbe4cc\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/OAppReceiver.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { IOAppReceiver, Origin } from \\\"./interfaces/IOAppReceiver.sol\\\";\\nimport { OAppCore } from \\\"./OAppCore.sol\\\";\\n\\n/**\\n * @title OAppReceiver\\n * @dev Abstract contract implementing the ILayerZeroReceiver interface and extending OAppCore for OApp receivers.\\n */\\nabstract contract OAppReceiver is IOAppReceiver, OAppCore {\\n // Custom error message for when the caller is not the registered endpoint/\\n error OnlyEndpoint(address addr);\\n\\n // @dev The version of the OAppReceiver implementation.\\n // @dev Version is bumped when changes are made to this contract.\\n uint64 internal constant RECEIVER_VERSION = 2;\\n\\n /**\\n * @notice Retrieves the OApp version information.\\n * @return senderVersion The version of the OAppSender.sol contract.\\n * @return receiverVersion The version of the OAppReceiver.sol contract.\\n *\\n * @dev Providing 0 as the default for OAppSender version. Indicates that the OAppSender is not implemented.\\n * ie. this is a RECEIVE only OApp.\\n * @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions.\\n */\\n function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) {\\n return (0, RECEIVER_VERSION);\\n }\\n\\n /**\\n * @notice Indicates whether an address is an approved composeMsg sender to the Endpoint.\\n * @dev _origin The origin information containing the source endpoint and sender address.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address on the src chain.\\n * - nonce: The nonce of the message.\\n * @dev _message The lzReceive payload.\\n * @param _sender The sender address.\\n * @return isSender Is a valid sender.\\n *\\n * @dev Applications can optionally choose to implement separate composeMsg senders that are NOT the bridging layer.\\n * @dev The default sender IS the OAppReceiver implementer.\\n */\\n function isComposeMsgSender(\\n Origin calldata /*_origin*/,\\n bytes calldata /*_message*/,\\n address _sender\\n ) public view virtual returns (bool) {\\n return _sender == address(this);\\n }\\n\\n /**\\n * @notice Checks if the path initialization is allowed based on the provided origin.\\n * @param origin The origin information containing the source endpoint and sender address.\\n * @return Whether the path has been initialized.\\n *\\n * @dev This indicates to the endpoint that the OApp has enabled msgs for this particular path to be received.\\n * @dev This defaults to assuming if a peer has been set, its initialized.\\n * Can be overridden by the OApp if there is other logic to determine this.\\n */\\n function allowInitializePath(Origin calldata origin) public view virtual returns (bool) {\\n return peers[origin.srcEid] == origin.sender;\\n }\\n\\n /**\\n * @notice Retrieves the next nonce for a given source endpoint and sender address.\\n * @dev _srcEid The source endpoint ID.\\n * @dev _sender The sender address.\\n * @return nonce The next nonce.\\n *\\n * @dev The path nonce starts from 1. If 0 is returned it means that there is NO nonce ordered enforcement.\\n * @dev Is required by the off-chain executor to determine the OApp expects msg execution is ordered.\\n * @dev This is also enforced by the OApp.\\n * @dev By default this is NOT enabled. ie. nextNonce is hardcoded to return 0.\\n */\\n function nextNonce(uint32 /*_srcEid*/, bytes32 /*_sender*/) public view virtual returns (uint64 nonce) {\\n return 0;\\n }\\n\\n /**\\n * @dev Entry point for receiving messages or packets from the endpoint.\\n * @param _origin The origin information containing the source endpoint and sender address.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address on the src chain.\\n * - nonce: The nonce of the message.\\n * @param _guid The unique identifier for the received LayerZero message.\\n * @param _message The payload of the received message.\\n * @param _executor The address of the executor for the received message.\\n * @param _extraData Additional arbitrary data provided by the corresponding executor.\\n *\\n * @dev Entry point for receiving msg/packet from the LayerZero endpoint.\\n */\\n function lzReceive(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address _executor,\\n bytes calldata _extraData\\n ) public payable virtual {\\n // Ensures that only the endpoint can attempt to lzReceive() messages to this OApp.\\n if (address(endpoint) != msg.sender) revert OnlyEndpoint(msg.sender);\\n\\n // Ensure that the sender matches the expected peer for the source endpoint.\\n if (_getPeerOrRevert(_origin.srcEid) != _origin.sender) revert OnlyPeer(_origin.srcEid, _origin.sender);\\n\\n // Call the internal OApp implementation of lzReceive.\\n _lzReceive(_origin, _guid, _message, _executor, _extraData);\\n }\\n\\n /**\\n * @dev Internal function to implement lzReceive logic without needing to copy the basic parameter validation.\\n */\\n function _lzReceive(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address _executor,\\n bytes calldata _extraData\\n ) internal virtual;\\n}\\n\",\"keccak256\":\"0x0174e9f1ec4cefe4b5adc26c392269c699b9ff75965364e5b7264426a462c70b\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/OAppSender.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { SafeERC20, IERC20 } from \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\nimport { MessagingParams, MessagingFee, MessagingReceipt } from \\\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\\\";\\nimport { OAppCore } from \\\"./OAppCore.sol\\\";\\n\\n/**\\n * @title OAppSender\\n * @dev Abstract contract implementing the OAppSender functionality for sending messages to a LayerZero endpoint.\\n */\\nabstract contract OAppSender is OAppCore {\\n using SafeERC20 for IERC20;\\n\\n // Custom error messages\\n error NotEnoughNative(uint256 msgValue);\\n error LzTokenUnavailable();\\n\\n // @dev The version of the OAppSender implementation.\\n // @dev Version is bumped when changes are made to this contract.\\n uint64 internal constant SENDER_VERSION = 1;\\n\\n /**\\n * @notice Retrieves the OApp version information.\\n * @return senderVersion The version of the OAppSender.sol contract.\\n * @return receiverVersion The version of the OAppReceiver.sol contract.\\n *\\n * @dev Providing 0 as the default for OAppReceiver version. Indicates that the OAppReceiver is not implemented.\\n * ie. this is a SEND only OApp.\\n * @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions\\n */\\n function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) {\\n return (SENDER_VERSION, 0);\\n }\\n\\n /**\\n * @dev Internal function to interact with the LayerZero EndpointV2.quote() for fee calculation.\\n * @param _dstEid The destination endpoint ID.\\n * @param _message The message payload.\\n * @param _options Additional options for the message.\\n * @param _payInLzToken Flag indicating whether to pay the fee in LZ tokens.\\n * @return fee The calculated MessagingFee for the message.\\n * - nativeFee: The native fee for the message.\\n * - lzTokenFee: The LZ token fee for the message.\\n */\\n function _quote(\\n uint32 _dstEid,\\n bytes memory _message,\\n bytes memory _options,\\n bool _payInLzToken\\n ) internal view virtual returns (MessagingFee memory fee) {\\n return\\n endpoint.quote(\\n MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _payInLzToken),\\n address(this)\\n );\\n }\\n\\n /**\\n * @dev Internal function to interact with the LayerZero EndpointV2.send() for sending a message.\\n * @param _dstEid The destination endpoint ID.\\n * @param _message The message payload.\\n * @param _options Additional options for the message.\\n * @param _fee The calculated LayerZero fee for the message.\\n * - nativeFee: The native fee.\\n * - lzTokenFee: The lzToken fee.\\n * @param _refundAddress The address to receive any excess fee values sent to the endpoint.\\n * @return receipt The receipt for the sent message.\\n * - guid: The unique identifier for the sent message.\\n * - nonce: The nonce of the sent message.\\n * - fee: The LayerZero fee incurred for the message.\\n */\\n function _lzSend(\\n uint32 _dstEid,\\n bytes memory _message,\\n bytes memory _options,\\n MessagingFee memory _fee,\\n address _refundAddress\\n ) internal virtual returns (MessagingReceipt memory receipt) {\\n // @dev Push corresponding fees to the endpoint, any excess is sent back to the _refundAddress from the endpoint.\\n uint256 messageValue = _payNative(_fee.nativeFee);\\n if (_fee.lzTokenFee > 0) _payLzToken(_fee.lzTokenFee);\\n\\n return\\n // solhint-disable-next-line check-send-result\\n endpoint.send{ value: messageValue }(\\n MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _fee.lzTokenFee > 0),\\n _refundAddress\\n );\\n }\\n\\n /**\\n * @dev Internal function to pay the native fee associated with the message.\\n * @param _nativeFee The native fee to be paid.\\n * @return nativeFee The amount of native currency paid.\\n *\\n * @dev If the OApp needs to initiate MULTIPLE LayerZero messages in a single transaction,\\n * this will need to be overridden because msg.value would contain multiple lzFees.\\n * @dev Should be overridden in the event the LayerZero endpoint requires a different native currency.\\n * @dev Some EVMs use an ERC20 as a method for paying transactions/gasFees.\\n * @dev The endpoint is EITHER/OR, ie. it will NOT support both types of native payment at a time.\\n */\\n function _payNative(uint256 _nativeFee) internal virtual returns (uint256 nativeFee) {\\n if (msg.value != _nativeFee) revert NotEnoughNative(msg.value);\\n return _nativeFee;\\n }\\n\\n /**\\n * @dev Internal function to pay the LZ token fee associated with the message.\\n * @param _lzTokenFee The LZ token fee to be paid.\\n *\\n * @dev If the caller is trying to pay in the specified lzToken, then the lzTokenFee is passed to the endpoint.\\n * @dev Any excess sent, is passed back to the specified _refundAddress in the _lzSend().\\n */\\n function _payLzToken(uint256 _lzTokenFee) internal virtual {\\n // @dev Cannot cache the token because it is not immutable in the endpoint.\\n address lzToken = endpoint.lzToken();\\n if (lzToken == address(0)) revert LzTokenUnavailable();\\n\\n // Pay LZ token fee by sending tokens to the endpoint.\\n IERC20(lzToken).safeTransferFrom(msg.sender, address(endpoint), _lzTokenFee);\\n }\\n}\\n\",\"keccak256\":\"0x518cf4adca601923ed4baa6619846a253ea32b8d8775f8bc1faa3dfac7f67c20\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppCore.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { ILayerZeroEndpointV2 } from \\\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\\\";\\n\\n/**\\n * @title IOAppCore\\n */\\ninterface IOAppCore {\\n // Custom error messages\\n error OnlyPeer(uint32 eid, bytes32 sender);\\n error NoPeer(uint32 eid);\\n error InvalidEndpointCall();\\n error InvalidDelegate();\\n\\n // Event emitted when a peer (OApp) is set for a corresponding endpoint\\n event PeerSet(uint32 eid, bytes32 peer);\\n\\n /**\\n * @notice Retrieves the OApp version information.\\n * @return senderVersion The version of the OAppSender.sol contract.\\n * @return receiverVersion The version of the OAppReceiver.sol contract.\\n */\\n function oAppVersion() external view returns (uint64 senderVersion, uint64 receiverVersion);\\n\\n /**\\n * @notice Retrieves the LayerZero endpoint associated with the OApp.\\n * @return iEndpoint The LayerZero endpoint as an interface.\\n */\\n function endpoint() external view returns (ILayerZeroEndpointV2 iEndpoint);\\n\\n /**\\n * @notice Retrieves the peer (OApp) associated with a corresponding endpoint.\\n * @param _eid The endpoint ID.\\n * @return peer The peer address (OApp instance) associated with the corresponding endpoint.\\n */\\n function peers(uint32 _eid) external view returns (bytes32 peer);\\n\\n /**\\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\\n * @param _eid The endpoint ID.\\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\\n */\\n function setPeer(uint32 _eid, bytes32 _peer) external;\\n\\n /**\\n * @notice Sets the delegate address for the OApp Core.\\n * @param _delegate The address of the delegate to be set.\\n */\\n function setDelegate(address _delegate) external;\\n}\\n\",\"keccak256\":\"0x40e49f2de74506e1da5dcaed53a39853f691647f4ceb0fccc8f49a68d3f47c58\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppMsgInspector.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\n/**\\n * @title IOAppMsgInspector\\n * @dev Interface for the OApp Message Inspector, allowing examination of message and options contents.\\n */\\ninterface IOAppMsgInspector {\\n // Custom error message for inspection failure\\n error InspectionFailed(bytes message, bytes options);\\n\\n /**\\n * @notice Allows the inspector to examine LayerZero message contents and optionally throw a revert if invalid.\\n * @param _message The message payload to be inspected.\\n * @param _options Additional options or parameters for inspection.\\n * @return valid A boolean indicating whether the inspection passed (true) or failed (false).\\n *\\n * @dev Optionally done as a revert, OR use the boolean provided to handle the failure.\\n */\\n function inspect(bytes calldata _message, bytes calldata _options) external view returns (bool valid);\\n}\\n\",\"keccak256\":\"0x339654e699043c400cad92de209aa23855ce10211c31cf4114042cc5224d3b7c\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppOptionsType3.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\n/**\\n * @dev Struct representing enforced option parameters.\\n */\\nstruct EnforcedOptionParam {\\n uint32 eid; // Endpoint ID\\n uint16 msgType; // Message Type\\n bytes options; // Additional options\\n}\\n\\n/**\\n * @title IOAppOptionsType3\\n * @dev Interface for the OApp with Type 3 Options, allowing the setting and combining of enforced options.\\n */\\ninterface IOAppOptionsType3 {\\n // Custom error message for invalid options\\n error InvalidOptions(bytes options);\\n\\n // Event emitted when enforced options are set\\n event EnforcedOptionSet(EnforcedOptionParam[] _enforcedOptions);\\n\\n /**\\n * @notice Sets enforced options for specific endpoint and message type combinations.\\n * @param _enforcedOptions An array of EnforcedOptionParam structures specifying enforced options.\\n */\\n function setEnforcedOptions(EnforcedOptionParam[] calldata _enforcedOptions) external;\\n\\n /**\\n * @notice Combines options for a given endpoint and message type.\\n * @param _eid The endpoint ID.\\n * @param _msgType The OApp message type.\\n * @param _extraOptions Additional options passed by the caller.\\n * @return options The combination of caller specified options AND enforced options.\\n */\\n function combineOptions(\\n uint32 _eid,\\n uint16 _msgType,\\n bytes calldata _extraOptions\\n ) external view returns (bytes memory options);\\n}\\n\",\"keccak256\":\"0x9fc08a51e9d7c9c710c4eb26f84fe77228305ad7da63fa486ff24ebf2f3bc461\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppReceiver.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.20;\\n\\nimport { ILayerZeroReceiver, Origin } from \\\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroReceiver.sol\\\";\\n\\ninterface IOAppReceiver is ILayerZeroReceiver {\\n /**\\n * @notice Indicates whether an address is an approved composeMsg sender to the Endpoint.\\n * @param _origin The origin information containing the source endpoint and sender address.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address on the src chain.\\n * - nonce: The nonce of the message.\\n * @param _message The lzReceive payload.\\n * @param _sender The sender address.\\n * @return isSender Is a valid sender.\\n *\\n * @dev Applications can optionally choose to implement a separate composeMsg sender that is NOT the bridging layer.\\n * @dev The default sender IS the OAppReceiver implementer.\\n */\\n function isComposeMsgSender(\\n Origin calldata _origin,\\n bytes calldata _message,\\n address _sender\\n ) external view returns (bool isSender);\\n}\\n\",\"keccak256\":\"0xd26135185e19b3732746d4a9e2923e896f28dec8664bab161faea2ee26fcdc3d\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { Ownable } from \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\nimport { IOAppOptionsType3, EnforcedOptionParam } from \\\"../interfaces/IOAppOptionsType3.sol\\\";\\n\\n/**\\n * @title OAppOptionsType3\\n * @dev Abstract contract implementing the IOAppOptionsType3 interface with type 3 options.\\n */\\nabstract contract OAppOptionsType3 is IOAppOptionsType3, Ownable {\\n uint16 internal constant OPTION_TYPE_3 = 3;\\n\\n // @dev The \\\"msgType\\\" should be defined in the child contract.\\n mapping(uint32 eid => mapping(uint16 msgType => bytes enforcedOption)) public enforcedOptions;\\n\\n /**\\n * @dev Sets the enforced options for specific endpoint and message type combinations.\\n * @param _enforcedOptions An array of EnforcedOptionParam structures specifying enforced options.\\n *\\n * @dev Only the owner/admin of the OApp can call this function.\\n * @dev Provides a way for the OApp to enforce things like paying for PreCrime, AND/OR minimum dst lzReceive gas amounts etc.\\n * @dev These enforced options can vary as the potential options/execution on the remote may differ as per the msgType.\\n * eg. Amount of lzReceive() gas necessary to deliver a lzCompose() message adds overhead you dont want to pay\\n * if you are only making a standard LayerZero message ie. lzReceive() WITHOUT sendCompose().\\n */\\n function setEnforcedOptions(EnforcedOptionParam[] calldata _enforcedOptions) public virtual onlyOwner {\\n _setEnforcedOptions(_enforcedOptions);\\n }\\n\\n /**\\n * @dev Sets the enforced options for specific endpoint and message type combinations.\\n * @param _enforcedOptions An array of EnforcedOptionParam structures specifying enforced options.\\n *\\n * @dev Provides a way for the OApp to enforce things like paying for PreCrime, AND/OR minimum dst lzReceive gas amounts etc.\\n * @dev These enforced options can vary as the potential options/execution on the remote may differ as per the msgType.\\n * eg. Amount of lzReceive() gas necessary to deliver a lzCompose() message adds overhead you dont want to pay\\n * if you are only making a standard LayerZero message ie. lzReceive() WITHOUT sendCompose().\\n */\\n function _setEnforcedOptions(EnforcedOptionParam[] memory _enforcedOptions) internal virtual {\\n for (uint256 i = 0; i < _enforcedOptions.length; i++) {\\n // @dev Enforced options are only available for optionType 3, as type 1 and 2 dont support combining.\\n _assertOptionsType3(_enforcedOptions[i].options);\\n enforcedOptions[_enforcedOptions[i].eid][_enforcedOptions[i].msgType] = _enforcedOptions[i].options;\\n }\\n\\n emit EnforcedOptionSet(_enforcedOptions);\\n }\\n\\n /**\\n * @notice Combines options for a given endpoint and message type.\\n * @param _eid The endpoint ID.\\n * @param _msgType The OAPP message type.\\n * @param _extraOptions Additional options passed by the caller.\\n * @return options The combination of caller specified options AND enforced options.\\n *\\n * @dev If there is an enforced lzReceive option:\\n * - {gasLimit: 200k, msg.value: 1 ether} AND a caller supplies a lzReceive option: {gasLimit: 100k, msg.value: 0.5 ether}\\n * - The resulting options will be {gasLimit: 300k, msg.value: 1.5 ether} when the message is executed on the remote lzReceive() function.\\n * @dev This presence of duplicated options is handled off-chain in the verifier/executor.\\n */\\n function combineOptions(\\n uint32 _eid,\\n uint16 _msgType,\\n bytes calldata _extraOptions\\n ) public view virtual returns (bytes memory) {\\n bytes memory enforced = enforcedOptions[_eid][_msgType];\\n\\n // No enforced options, pass whatever the caller supplied, even if it's empty or legacy type 1/2 options.\\n if (enforced.length == 0) return _extraOptions;\\n\\n // No caller options, return enforced\\n if (_extraOptions.length == 0) return enforced;\\n\\n // @dev If caller provided _extraOptions, must be type 3 as its the ONLY type that can be combined.\\n if (_extraOptions.length >= 2) {\\n _assertOptionsType3(_extraOptions);\\n // @dev Remove the first 2 bytes containing the type from the _extraOptions and combine with enforced.\\n return bytes.concat(enforced, _extraOptions[2:]);\\n }\\n\\n // No valid set of options was found.\\n revert InvalidOptions(_extraOptions);\\n }\\n\\n /**\\n * @dev Internal function to assert that options are of type 3.\\n * @param _options The options to be checked.\\n */\\n function _assertOptionsType3(bytes memory _options) internal pure virtual {\\n uint16 optionsType;\\n assembly {\\n optionsType := mload(add(_options, 2))\\n }\\n if (optionsType != OPTION_TYPE_3) revert InvalidOptions(_options);\\n }\\n}\\n\",\"keccak256\":\"0x5275636cd47e660a2fdf6c7fe9d41ff3cc866b785cc8a9d88c1b8ca983509f01\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/precrime/OAppPreCrimeSimulator.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { Ownable } from \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\nimport { IPreCrime } from \\\"./interfaces/IPreCrime.sol\\\";\\nimport { IOAppPreCrimeSimulator, InboundPacket, Origin } from \\\"./interfaces/IOAppPreCrimeSimulator.sol\\\";\\n\\n/**\\n * @title OAppPreCrimeSimulator\\n * @dev Abstract contract serving as the base for preCrime simulation functionality in an OApp.\\n */\\nabstract contract OAppPreCrimeSimulator is IOAppPreCrimeSimulator, Ownable {\\n // The address of the preCrime implementation.\\n address public preCrime;\\n\\n /**\\n * @dev Retrieves the address of the OApp contract.\\n * @return The address of the OApp contract.\\n *\\n * @dev The simulator contract is the base contract for the OApp by default.\\n * @dev If the simulator is a separate contract, override this function.\\n */\\n function oApp() external view virtual returns (address) {\\n return address(this);\\n }\\n\\n /**\\n * @dev Sets the preCrime contract address.\\n * @param _preCrime The address of the preCrime contract.\\n */\\n function setPreCrime(address _preCrime) public virtual onlyOwner {\\n preCrime = _preCrime;\\n emit PreCrimeSet(_preCrime);\\n }\\n\\n /**\\n * @dev Interface for pre-crime simulations. Always reverts at the end with the simulation results.\\n * @param _packets An array of InboundPacket objects representing received packets to be delivered.\\n *\\n * @dev WARNING: MUST revert at the end with the simulation results.\\n * @dev Gives the preCrime implementation the ability to mock sending packets to the lzReceive function,\\n * WITHOUT actually executing them.\\n */\\n function lzReceiveAndRevert(InboundPacket[] calldata _packets) public payable virtual {\\n for (uint256 i = 0; i < _packets.length; i++) {\\n InboundPacket calldata packet = _packets[i];\\n\\n // Ignore packets that are not from trusted peers.\\n if (!isPeer(packet.origin.srcEid, packet.origin.sender)) continue;\\n\\n // @dev Because a verifier is calling this function, it doesnt have access to executor params:\\n // - address _executor\\n // - bytes calldata _extraData\\n // preCrime will NOT work for OApps that rely on these two parameters inside of their _lzReceive().\\n // They are instead stubbed to default values, address(0) and bytes(\\\"\\\")\\n // @dev Calling this.lzReceiveSimulate removes ability for assembly return 0 callstack exit,\\n // which would cause the revert to be ignored.\\n this.lzReceiveSimulate{ value: packet.value }(\\n packet.origin,\\n packet.guid,\\n packet.message,\\n packet.executor,\\n packet.extraData\\n );\\n }\\n\\n // @dev Revert with the simulation results. msg.sender must implement IPreCrime.buildSimulationResult().\\n revert SimulationResult(IPreCrime(msg.sender).buildSimulationResult());\\n }\\n\\n /**\\n * @dev Is effectively an internal function because msg.sender must be address(this).\\n * Allows resetting the call stack for 'internal' calls.\\n * @param _origin The origin information containing the source endpoint and sender address.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address on the src chain.\\n * - nonce: The nonce of the message.\\n * @param _guid The unique identifier of the packet.\\n * @param _message The message payload of the packet.\\n * @param _executor The executor address for the packet.\\n * @param _extraData Additional data for the packet.\\n */\\n function lzReceiveSimulate(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address _executor,\\n bytes calldata _extraData\\n ) external payable virtual {\\n // @dev Ensure ONLY can be called 'internally'.\\n if (msg.sender != address(this)) revert OnlySelf();\\n _lzReceiveSimulate(_origin, _guid, _message, _executor, _extraData);\\n }\\n\\n /**\\n * @dev Internal function to handle the OAppPreCrimeSimulator simulated receive.\\n * @param _origin The origin information.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address from the src chain.\\n * - nonce: The nonce of the LayerZero message.\\n * @param _guid The GUID of the LayerZero message.\\n * @param _message The LayerZero message.\\n * @param _executor The address of the off-chain executor.\\n * @param _extraData Arbitrary data passed by the msg executor.\\n *\\n * @dev Enables the preCrime simulator to mock sending lzReceive() messages,\\n * routes the msg down from the OAppPreCrimeSimulator, and back up to the OAppReceiver.\\n */\\n function _lzReceiveSimulate(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address _executor,\\n bytes calldata _extraData\\n ) internal virtual;\\n\\n /**\\n * @dev checks if the specified peer is considered 'trusted' by the OApp.\\n * @param _eid The endpoint Id to check.\\n * @param _peer The peer to check.\\n * @return Whether the peer passed is considered 'trusted' by the OApp.\\n */\\n function isPeer(uint32 _eid, bytes32 _peer) public view virtual returns (bool);\\n}\\n\",\"keccak256\":\"0x205a0abfd8b3c9af2740769f251381b84999b8e9347f3cd50de3ef8290a17750\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/precrime/interfaces/IOAppPreCrimeSimulator.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\n// @dev Import the Origin so it's exposed to OAppPreCrimeSimulator implementers.\\n// solhint-disable-next-line no-unused-import\\nimport { InboundPacket, Origin } from \\\"../libs/Packet.sol\\\";\\n\\n/**\\n * @title IOAppPreCrimeSimulator Interface\\n * @dev Interface for the preCrime simulation functionality in an OApp.\\n */\\ninterface IOAppPreCrimeSimulator {\\n // @dev simulation result used in PreCrime implementation\\n error SimulationResult(bytes result);\\n error OnlySelf();\\n\\n /**\\n * @dev Emitted when the preCrime contract address is set.\\n * @param preCrimeAddress The address of the preCrime contract.\\n */\\n event PreCrimeSet(address preCrimeAddress);\\n\\n /**\\n * @dev Retrieves the address of the preCrime contract implementation.\\n * @return The address of the preCrime contract.\\n */\\n function preCrime() external view returns (address);\\n\\n /**\\n * @dev Retrieves the address of the OApp contract.\\n * @return The address of the OApp contract.\\n */\\n function oApp() external view returns (address);\\n\\n /**\\n * @dev Sets the preCrime contract address.\\n * @param _preCrime The address of the preCrime contract.\\n */\\n function setPreCrime(address _preCrime) external;\\n\\n /**\\n * @dev Mocks receiving a packet, then reverts with a series of data to infer the state/result.\\n * @param _packets An array of LayerZero InboundPacket objects representing received packets.\\n */\\n function lzReceiveAndRevert(InboundPacket[] calldata _packets) external payable;\\n\\n /**\\n * @dev checks if the specified peer is considered 'trusted' by the OApp.\\n * @param _eid The endpoint Id to check.\\n * @param _peer The peer to check.\\n * @return Whether the peer passed is considered 'trusted' by the OApp.\\n */\\n function isPeer(uint32 _eid, bytes32 _peer) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x5d24db150949ea8e6437178e65a942e8c8b7f332e5daf32750f56b23b35b5bb2\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/precrime/interfaces/IPreCrime.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\nstruct PreCrimePeer {\\n uint32 eid;\\n bytes32 preCrime;\\n bytes32 oApp;\\n}\\n\\n// TODO not done yet\\ninterface IPreCrime {\\n error OnlyOffChain();\\n\\n // for simulate()\\n error PacketOversize(uint256 max, uint256 actual);\\n error PacketUnsorted();\\n error SimulationFailed(bytes reason);\\n\\n // for preCrime()\\n error SimulationResultNotFound(uint32 eid);\\n error InvalidSimulationResult(uint32 eid, bytes reason);\\n error CrimeFound(bytes crime);\\n\\n function getConfig(bytes[] calldata _packets, uint256[] calldata _packetMsgValues) external returns (bytes memory);\\n\\n function simulate(\\n bytes[] calldata _packets,\\n uint256[] calldata _packetMsgValues\\n ) external payable returns (bytes memory);\\n\\n function buildSimulationResult() external view returns (bytes memory);\\n\\n function preCrime(\\n bytes[] calldata _packets,\\n uint256[] calldata _packetMsgValues,\\n bytes[] calldata _simulations\\n ) external;\\n\\n function version() external view returns (uint64 major, uint8 minor);\\n}\\n\",\"keccak256\":\"0xc8d869f27ef8ceb2e13fdf6a70682fd4dee3f90c4924eb8e125bc1e66cb6af84\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/precrime/libs/Packet.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { Origin } from \\\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\\\";\\nimport { PacketV1Codec } from \\\"@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.sol\\\";\\n\\n/**\\n * @title InboundPacket\\n * @dev Structure representing an inbound packet received by the contract.\\n */\\nstruct InboundPacket {\\n Origin origin; // Origin information of the packet.\\n uint32 dstEid; // Destination endpointId of the packet.\\n address receiver; // Receiver address for the packet.\\n bytes32 guid; // Unique identifier of the packet.\\n uint256 value; // msg.value of the packet.\\n address executor; // Executor address for the packet.\\n bytes message; // Message payload of the packet.\\n bytes extraData; // Additional arbitrary data for the packet.\\n}\\n\\n/**\\n * @title PacketDecoder\\n * @dev Library for decoding LayerZero packets.\\n */\\nlibrary PacketDecoder {\\n using PacketV1Codec for bytes;\\n\\n /**\\n * @dev Decode an inbound packet from the given packet data.\\n * @param _packet The packet data to decode.\\n * @return packet An InboundPacket struct representing the decoded packet.\\n */\\n function decode(bytes calldata _packet) internal pure returns (InboundPacket memory packet) {\\n packet.origin = Origin(_packet.srcEid(), _packet.sender(), _packet.nonce());\\n packet.dstEid = _packet.dstEid();\\n packet.receiver = _packet.receiverB20();\\n packet.guid = _packet.guid();\\n packet.message = _packet.message();\\n }\\n\\n /**\\n * @dev Decode multiple inbound packets from the given packet data and associated message values.\\n * @param _packets An array of packet data to decode.\\n * @param _packetMsgValues An array of associated message values for each packet.\\n * @return packets An array of InboundPacket structs representing the decoded packets.\\n */\\n function decode(\\n bytes[] calldata _packets,\\n uint256[] memory _packetMsgValues\\n ) internal pure returns (InboundPacket[] memory packets) {\\n packets = new InboundPacket[](_packets.length);\\n for (uint256 i = 0; i < _packets.length; i++) {\\n bytes calldata packet = _packets[i];\\n packets[i] = PacketDecoder.decode(packet);\\n // @dev Allows the verifier to specify the msg.value that gets passed in lzReceive.\\n packets[i].value = _packetMsgValues[i];\\n }\\n }\\n}\\n\",\"keccak256\":\"0xcb2fb1c5b2eb3731de78b479b9c2ab3bba326fe0b0b3a008590f18e881e457a6\",\"license\":\"MIT\"},\"@layerzerolabs/oft-evm/contracts/OFT.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { ERC20 } from \\\"@openzeppelin/contracts/token/ERC20/ERC20.sol\\\";\\nimport { IOFT, OFTCore } from \\\"./OFTCore.sol\\\";\\n\\n/**\\n * @title OFT Contract\\n * @dev OFT is an ERC-20 token that extends the functionality of the OFTCore contract.\\n */\\nabstract contract OFT is OFTCore, ERC20 {\\n /**\\n * @dev Constructor for the OFT contract.\\n * @param _name The name of the OFT.\\n * @param _symbol The symbol of the OFT.\\n * @param _lzEndpoint The LayerZero endpoint address.\\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\\n */\\n constructor(\\n string memory _name,\\n string memory _symbol,\\n address _lzEndpoint,\\n address _delegate\\n ) ERC20(_name, _symbol) OFTCore(decimals(), _lzEndpoint, _delegate) {}\\n\\n /**\\n * @dev Retrieves the address of the underlying ERC20 implementation.\\n * @return The address of the OFT token.\\n *\\n * @dev In the case of OFT, address(this) and erc20 are the same contract.\\n */\\n function token() public view returns (address) {\\n return address(this);\\n }\\n\\n /**\\n * @notice Indicates whether the OFT contract requires approval of the 'token()' to send.\\n * @return requiresApproval Needs approval of the underlying token implementation.\\n *\\n * @dev In the case of OFT where the contract IS the token, approval is NOT required.\\n */\\n function approvalRequired() external pure virtual returns (bool) {\\n return false;\\n }\\n\\n /**\\n * @dev Burns tokens from the sender's specified balance.\\n * @param _from The address to debit the tokens from.\\n * @param _amountLD The amount of tokens to send in local decimals.\\n * @param _minAmountLD The minimum amount to send in local decimals.\\n * @param _dstEid The destination chain ID.\\n * @return amountSentLD The amount sent in local decimals.\\n * @return amountReceivedLD The amount received in local decimals on the remote.\\n */\\n function _debit(\\n address _from,\\n uint256 _amountLD,\\n uint256 _minAmountLD,\\n uint32 _dstEid\\n ) internal virtual override returns (uint256 amountSentLD, uint256 amountReceivedLD) {\\n (amountSentLD, amountReceivedLD) = _debitView(_amountLD, _minAmountLD, _dstEid);\\n\\n // @dev In NON-default OFT, amountSentLD could be 100, with a 10% fee, the amountReceivedLD amount is 90,\\n // therefore amountSentLD CAN differ from amountReceivedLD.\\n\\n // @dev Default OFT burns on src.\\n _burn(_from, amountSentLD);\\n }\\n\\n /**\\n * @dev Credits tokens to the specified address.\\n * @param _to The address to credit the tokens to.\\n * @param _amountLD The amount of tokens to credit in local decimals.\\n * @dev _srcEid The source chain ID.\\n * @return amountReceivedLD The amount of tokens ACTUALLY received in local decimals.\\n */\\n function _credit(\\n address _to,\\n uint256 _amountLD,\\n uint32 /*_srcEid*/\\n ) internal virtual override returns (uint256 amountReceivedLD) {\\n if (_to == address(0x0)) _to = address(0xdead); // _mint(...) does not support address(0x0)\\n // @dev Default OFT mints on dst.\\n _mint(_to, _amountLD);\\n // @dev In the case of NON-default OFT, the _amountLD MIGHT not be == amountReceivedLD.\\n return _amountLD;\\n }\\n}\\n\",\"keccak256\":\"0xdc3582e4a20e02a79050c17058a1f1f42a4335d1a70be06c0a52a3fb05d4c89a\",\"license\":\"MIT\"},\"@layerzerolabs/oft-evm/contracts/OFTCore.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { IERC20 } from \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\n\\nimport { OApp, Origin } from \\\"@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol\\\";\\nimport { OAppOptionsType3 } from \\\"@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol\\\";\\nimport { IOAppMsgInspector } from \\\"@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppMsgInspector.sol\\\";\\n\\nimport { OAppPreCrimeSimulator } from \\\"@layerzerolabs/oapp-evm/contracts/precrime/OAppPreCrimeSimulator.sol\\\";\\n\\nimport { IOFT, SendParam, OFTLimit, OFTReceipt, OFTFeeDetail, MessagingReceipt, MessagingFee } from \\\"./interfaces/IOFT.sol\\\";\\nimport { OFTMsgCodec } from \\\"./libs/OFTMsgCodec.sol\\\";\\nimport { OFTComposeMsgCodec } from \\\"./libs/OFTComposeMsgCodec.sol\\\";\\n\\n/**\\n * @title OFTCore\\n * @dev Abstract contract for the OftChain (OFT) token.\\n */\\nabstract contract OFTCore is IOFT, OApp, OAppPreCrimeSimulator, OAppOptionsType3 {\\n using OFTMsgCodec for bytes;\\n using OFTMsgCodec for bytes32;\\n\\n // @notice Provides a conversion rate when swapping between denominations of SD and LD\\n // - shareDecimals == SD == shared Decimals\\n // - localDecimals == LD == local decimals\\n // @dev Considers that tokens have different decimal amounts on various chains.\\n // @dev eg.\\n // For a token\\n // - locally with 4 decimals --> 1.2345 => uint(12345)\\n // - remotely with 2 decimals --> 1.23 => uint(123)\\n // - The conversion rate would be 10 ** (4 - 2) = 100\\n // @dev If you want to send 1.2345 -> (uint 12345), you CANNOT represent that value on the remote,\\n // you can only display 1.23 -> uint(123).\\n // @dev To preserve the dust that would otherwise be lost on that conversion,\\n // we need to unify a denomination that can be represented on ALL chains inside of the OFT mesh\\n uint256 public immutable decimalConversionRate;\\n\\n // @notice Msg types that are used to identify the various OFT operations.\\n // @dev This can be extended in child contracts for non-default oft operations\\n // @dev These values are used in things like combineOptions() in OAppOptionsType3.sol.\\n uint16 public constant SEND = 1;\\n uint16 public constant SEND_AND_CALL = 2;\\n\\n // Address of an optional contract to inspect both 'message' and 'options'\\n address public msgInspector;\\n event MsgInspectorSet(address inspector);\\n\\n /**\\n * @dev Constructor.\\n * @param _localDecimals The decimals of the token on the local chain (this chain).\\n * @param _endpoint The address of the LayerZero endpoint.\\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\\n */\\n constructor(uint8 _localDecimals, address _endpoint, address _delegate) OApp(_endpoint, _delegate) {\\n if (_localDecimals < sharedDecimals()) revert InvalidLocalDecimals();\\n decimalConversionRate = 10 ** (_localDecimals - sharedDecimals());\\n }\\n\\n /**\\n * @notice Retrieves interfaceID and the version of the OFT.\\n * @return interfaceId The interface ID.\\n * @return version The version.\\n *\\n * @dev interfaceId: This specific interface ID is '0x02e49c2c'.\\n * @dev version: Indicates a cross-chain compatible msg encoding with other OFTs.\\n * @dev If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented.\\n * ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)\\n */\\n function oftVersion() external pure virtual returns (bytes4 interfaceId, uint64 version) {\\n return (type(IOFT).interfaceId, 1);\\n }\\n\\n /**\\n * @dev Retrieves the shared decimals of the OFT.\\n * @return The shared decimals of the OFT.\\n *\\n * @dev Sets an implicit cap on the amount of tokens, over uint64.max() will need some sort of outbound cap / totalSupply cap\\n * Lowest common decimal denominator between chains.\\n * Defaults to 6 decimal places to provide up to 18,446,744,073,709.551615 units (max uint64).\\n * For tokens exceeding this totalSupply(), they will need to override the sharedDecimals function with something smaller.\\n * ie. 4 sharedDecimals would be 1,844,674,407,370,955.1615\\n */\\n function sharedDecimals() public view virtual returns (uint8) {\\n return 6;\\n }\\n\\n /**\\n * @dev Sets the message inspector address for the OFT.\\n * @param _msgInspector The address of the message inspector.\\n *\\n * @dev This is an optional contract that can be used to inspect both 'message' and 'options'.\\n * @dev Set it to address(0) to disable it, or set it to a contract address to enable it.\\n */\\n function setMsgInspector(address _msgInspector) public virtual onlyOwner {\\n msgInspector = _msgInspector;\\n emit MsgInspectorSet(_msgInspector);\\n }\\n\\n /**\\n * @notice Provides the fee breakdown and settings data for an OFT. Unused in the default implementation.\\n * @param _sendParam The parameters for the send operation.\\n * @return oftLimit The OFT limit information.\\n * @return oftFeeDetails The details of OFT fees.\\n * @return oftReceipt The OFT receipt information.\\n */\\n function quoteOFT(\\n SendParam calldata _sendParam\\n )\\n external\\n view\\n virtual\\n returns (OFTLimit memory oftLimit, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory oftReceipt)\\n {\\n uint256 minAmountLD = 0; // Unused in the default implementation.\\n uint256 maxAmountLD = IERC20(this.token()).totalSupply(); // Unused in the default implementation.\\n oftLimit = OFTLimit(minAmountLD, maxAmountLD);\\n\\n // Unused in the default implementation; reserved for future complex fee details.\\n oftFeeDetails = new OFTFeeDetail[](0);\\n\\n // @dev This is the same as the send() operation, but without the actual send.\\n // - amountSentLD is the amount in local decimals that would be sent from the sender.\\n // - amountReceivedLD is the amount in local decimals that will be credited to the recipient on the remote OFT instance.\\n // @dev The amountSentLD MIGHT not equal the amount the user actually receives. HOWEVER, the default does.\\n (uint256 amountSentLD, uint256 amountReceivedLD) = _debitView(\\n _sendParam.amountLD,\\n _sendParam.minAmountLD,\\n _sendParam.dstEid\\n );\\n oftReceipt = OFTReceipt(amountSentLD, amountReceivedLD);\\n }\\n\\n /**\\n * @notice Provides a quote for the send() operation.\\n * @param _sendParam The parameters for the send() operation.\\n * @param _payInLzToken Flag indicating whether the caller is paying in the LZ token.\\n * @return msgFee The calculated LayerZero messaging fee from the send() operation.\\n *\\n * @dev MessagingFee: LayerZero msg fee\\n * - nativeFee: The native fee.\\n * - lzTokenFee: The lzToken fee.\\n */\\n function quoteSend(\\n SendParam calldata _sendParam,\\n bool _payInLzToken\\n ) external view virtual returns (MessagingFee memory msgFee) {\\n // @dev mock the amount to receive, this is the same operation used in the send().\\n // The quote is as similar as possible to the actual send() operation.\\n (, uint256 amountReceivedLD) = _debitView(_sendParam.amountLD, _sendParam.minAmountLD, _sendParam.dstEid);\\n\\n // @dev Builds the options and OFT message to quote in the endpoint.\\n (bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam, amountReceivedLD);\\n\\n // @dev Calculates the LayerZero fee for the send() operation.\\n return _quote(_sendParam.dstEid, message, options, _payInLzToken);\\n }\\n\\n /**\\n * @dev Executes the send operation.\\n * @param _sendParam The parameters for the send operation.\\n * @param _fee The calculated fee for the send() operation.\\n * - nativeFee: The native fee.\\n * - lzTokenFee: The lzToken fee.\\n * @param _refundAddress The address to receive any excess funds.\\n * @return msgReceipt The receipt for the send operation.\\n * @return oftReceipt The OFT receipt information.\\n *\\n * @dev MessagingReceipt: LayerZero msg receipt\\n * - guid: The unique identifier for the sent message.\\n * - nonce: The nonce of the sent message.\\n * - fee: The LayerZero fee incurred for the message.\\n */\\n function send(\\n SendParam calldata _sendParam,\\n MessagingFee calldata _fee,\\n address _refundAddress\\n ) external payable virtual returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) {\\n return _send(_sendParam, _fee, _refundAddress);\\n }\\n\\n /**\\n * @dev Internal function to execute the send operation.\\n * @param _sendParam The parameters for the send operation.\\n * @param _fee The calculated fee for the send() operation.\\n * - nativeFee: The native fee.\\n * - lzTokenFee: The lzToken fee.\\n * @param _refundAddress The address to receive any excess funds.\\n * @return msgReceipt The receipt for the send operation.\\n * @return oftReceipt The OFT receipt information.\\n *\\n * @dev MessagingReceipt: LayerZero msg receipt\\n * - guid: The unique identifier for the sent message.\\n * - nonce: The nonce of the sent message.\\n * - fee: The LayerZero fee incurred for the message.\\n */\\n function _send(\\n SendParam calldata _sendParam,\\n MessagingFee calldata _fee,\\n address _refundAddress\\n ) internal virtual returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) {\\n // @dev Applies the token transfers regarding this send() operation.\\n // - amountSentLD is the amount in local decimals that was ACTUALLY sent/debited from the sender.\\n // - amountReceivedLD is the amount in local decimals that will be received/credited to the recipient on the remote OFT instance.\\n (uint256 amountSentLD, uint256 amountReceivedLD) = _debit(\\n msg.sender,\\n _sendParam.amountLD,\\n _sendParam.minAmountLD,\\n _sendParam.dstEid\\n );\\n\\n // @dev Builds the options and OFT message to quote in the endpoint.\\n (bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam, amountReceivedLD);\\n\\n // @dev Sends the message to the LayerZero endpoint and returns the LayerZero msg receipt.\\n msgReceipt = _lzSend(_sendParam.dstEid, message, options, _fee, _refundAddress);\\n // @dev Formulate the OFT receipt.\\n oftReceipt = OFTReceipt(amountSentLD, amountReceivedLD);\\n\\n emit OFTSent(msgReceipt.guid, _sendParam.dstEid, msg.sender, amountSentLD, amountReceivedLD);\\n }\\n\\n /**\\n * @dev Internal function to build the message and options.\\n * @param _sendParam The parameters for the send() operation.\\n * @param _amountLD The amount in local decimals.\\n * @return message The encoded message.\\n * @return options The encoded options.\\n */\\n function _buildMsgAndOptions(\\n SendParam calldata _sendParam,\\n uint256 _amountLD\\n ) internal view virtual returns (bytes memory message, bytes memory options) {\\n bool hasCompose;\\n // @dev This generated message has the msg.sender encoded into the payload so the remote knows who the caller is.\\n (message, hasCompose) = OFTMsgCodec.encode(\\n _sendParam.to,\\n _toSD(_amountLD),\\n // @dev Must be include a non empty bytes if you want to compose, EVEN if you dont need it on the remote.\\n // EVEN if you dont require an arbitrary payload to be sent... eg. '0x01'\\n _sendParam.composeMsg\\n );\\n // @dev Change the msg type depending if its composed or not.\\n uint16 msgType = hasCompose ? SEND_AND_CALL : SEND;\\n // @dev Combine the callers _extraOptions with the enforced options via the OAppOptionsType3.\\n options = combineOptions(_sendParam.dstEid, msgType, _sendParam.extraOptions);\\n\\n // @dev Optionally inspect the message and options depending if the OApp owner has set a msg inspector.\\n // @dev If it fails inspection, needs to revert in the implementation. ie. does not rely on return boolean\\n address inspector = msgInspector; // caches the msgInspector to avoid potential double storage read\\n if (inspector != address(0)) IOAppMsgInspector(inspector).inspect(message, options);\\n }\\n\\n /**\\n * @dev Internal function to handle the receive on the LayerZero endpoint.\\n * @param _origin The origin information.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address from the src chain.\\n * - nonce: The nonce of the LayerZero message.\\n * @param _guid The unique identifier for the received LayerZero message.\\n * @param _message The encoded message.\\n * @dev _executor The address of the executor.\\n * @dev _extraData Additional data.\\n */\\n function _lzReceive(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address /*_executor*/, // @dev unused in the default implementation.\\n bytes calldata /*_extraData*/ // @dev unused in the default implementation.\\n ) internal virtual override {\\n // @dev The src sending chain doesnt know the address length on this chain (potentially non-evm)\\n // Thus everything is bytes32() encoded in flight.\\n address toAddress = _message.sendTo().bytes32ToAddress();\\n // @dev Credit the amountLD to the recipient and return the ACTUAL amount the recipient received in local decimals\\n uint256 amountReceivedLD = _credit(toAddress, _toLD(_message.amountSD()), _origin.srcEid);\\n\\n if (_message.isComposed()) {\\n // @dev Proprietary composeMsg format for the OFT.\\n bytes memory composeMsg = OFTComposeMsgCodec.encode(\\n _origin.nonce,\\n _origin.srcEid,\\n amountReceivedLD,\\n _message.composeMsg()\\n );\\n\\n // @dev Stores the lzCompose payload that will be executed in a separate tx.\\n // Standardizes functionality for executing arbitrary contract invocation on some non-evm chains.\\n // @dev The off-chain executor will listen and process the msg based on the src-chain-callers compose options passed.\\n // @dev The index is used when a OApp needs to compose multiple msgs on lzReceive.\\n // For default OFT implementation there is only 1 compose msg per lzReceive, thus its always 0.\\n endpoint.sendCompose(toAddress, _guid, 0 /* the index of the composed message*/, composeMsg);\\n }\\n\\n emit OFTReceived(_guid, _origin.srcEid, toAddress, amountReceivedLD);\\n }\\n\\n /**\\n * @dev Internal function to handle the OAppPreCrimeSimulator simulated receive.\\n * @param _origin The origin information.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address from the src chain.\\n * - nonce: The nonce of the LayerZero message.\\n * @param _guid The unique identifier for the received LayerZero message.\\n * @param _message The LayerZero message.\\n * @param _executor The address of the off-chain executor.\\n * @param _extraData Arbitrary data passed by the msg executor.\\n *\\n * @dev Enables the preCrime simulator to mock sending lzReceive() messages,\\n * routes the msg down from the OAppPreCrimeSimulator, and back up to the OAppReceiver.\\n */\\n function _lzReceiveSimulate(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address _executor,\\n bytes calldata _extraData\\n ) internal virtual override {\\n _lzReceive(_origin, _guid, _message, _executor, _extraData);\\n }\\n\\n /**\\n * @dev Check if the peer is considered 'trusted' by the OApp.\\n * @param _eid The endpoint ID to check.\\n * @param _peer The peer to check.\\n * @return Whether the peer passed is considered 'trusted' by the OApp.\\n *\\n * @dev Enables OAppPreCrimeSimulator to check whether a potential Inbound Packet is from a trusted source.\\n */\\n function isPeer(uint32 _eid, bytes32 _peer) public view virtual override returns (bool) {\\n return peers[_eid] == _peer;\\n }\\n\\n /**\\n * @dev Internal function to remove dust from the given local decimal amount.\\n * @param _amountLD The amount in local decimals.\\n * @return amountLD The amount after removing dust.\\n *\\n * @dev Prevents the loss of dust when moving amounts between chains with different decimals.\\n * @dev eg. uint(123) with a conversion rate of 100 becomes uint(100).\\n */\\n function _removeDust(uint256 _amountLD) internal view virtual returns (uint256 amountLD) {\\n return (_amountLD / decimalConversionRate) * decimalConversionRate;\\n }\\n\\n /**\\n * @dev Internal function to convert an amount from shared decimals into local decimals.\\n * @param _amountSD The amount in shared decimals.\\n * @return amountLD The amount in local decimals.\\n */\\n function _toLD(uint64 _amountSD) internal view virtual returns (uint256 amountLD) {\\n return _amountSD * decimalConversionRate;\\n }\\n\\n /**\\n * @dev Internal function to convert an amount from local decimals into shared decimals.\\n * @param _amountLD The amount in local decimals.\\n * @return amountSD The amount in shared decimals.\\n *\\n * @dev Reverts if the _amountLD in shared decimals overflows uint64.\\n * @dev eg. uint(2**64 + 123) with a conversion rate of 1 wraps around 2**64 to uint(123).\\n */\\n function _toSD(uint256 _amountLD) internal view virtual returns (uint64 amountSD) {\\n uint256 _amountSD = _amountLD / decimalConversionRate;\\n if (_amountSD > type(uint64).max) revert AmountSDOverflowed(_amountSD);\\n return uint64(_amountSD);\\n }\\n\\n /**\\n * @dev Internal function to mock the amount mutation from a OFT debit() operation.\\n * @param _amountLD The amount to send in local decimals.\\n * @param _minAmountLD The minimum amount to send in local decimals.\\n * @dev _dstEid The destination endpoint ID.\\n * @return amountSentLD The amount sent, in local decimals.\\n * @return amountReceivedLD The amount to be received on the remote chain, in local decimals.\\n *\\n * @dev This is where things like fees would be calculated and deducted from the amount to be received on the remote.\\n */\\n function _debitView(\\n uint256 _amountLD,\\n uint256 _minAmountLD,\\n uint32 /*_dstEid*/\\n ) internal view virtual returns (uint256 amountSentLD, uint256 amountReceivedLD) {\\n // @dev Remove the dust so nothing is lost on the conversion between chains with different decimals for the token.\\n amountSentLD = _removeDust(_amountLD);\\n // @dev The amount to send is the same as amount received in the default implementation.\\n amountReceivedLD = amountSentLD;\\n\\n // @dev Check for slippage.\\n if (amountReceivedLD < _minAmountLD) {\\n revert SlippageExceeded(amountReceivedLD, _minAmountLD);\\n }\\n }\\n\\n /**\\n * @dev Internal function to perform a debit operation.\\n * @param _from The address to debit.\\n * @param _amountLD The amount to send in local decimals.\\n * @param _minAmountLD The minimum amount to send in local decimals.\\n * @param _dstEid The destination endpoint ID.\\n * @return amountSentLD The amount sent in local decimals.\\n * @return amountReceivedLD The amount received in local decimals on the remote.\\n *\\n * @dev Defined here but are intended to be overriden depending on the OFT implementation.\\n * @dev Depending on OFT implementation the _amountLD could differ from the amountReceivedLD.\\n */\\n function _debit(\\n address _from,\\n uint256 _amountLD,\\n uint256 _minAmountLD,\\n uint32 _dstEid\\n ) internal virtual returns (uint256 amountSentLD, uint256 amountReceivedLD);\\n\\n /**\\n * @dev Internal function to perform a credit operation.\\n * @param _to The address to credit.\\n * @param _amountLD The amount to credit in local decimals.\\n * @param _srcEid The source endpoint ID.\\n * @return amountReceivedLD The amount ACTUALLY received in local decimals.\\n *\\n * @dev Defined here but are intended to be overriden depending on the OFT implementation.\\n * @dev Depending on OFT implementation the _amountLD could differ from the amountReceivedLD.\\n */\\n function _credit(\\n address _to,\\n uint256 _amountLD,\\n uint32 _srcEid\\n ) internal virtual returns (uint256 amountReceivedLD);\\n}\\n\",\"keccak256\":\"0xdda89798c66928bba9e0fa44b3edf4710ff15cf46edadcf3e15c92d78fcc9ca8\",\"license\":\"MIT\"},\"@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { MessagingReceipt, MessagingFee } from \\\"@layerzerolabs/oapp-evm/contracts/oapp/OAppSender.sol\\\";\\n\\n/**\\n * @dev Struct representing token parameters for the OFT send() operation.\\n */\\nstruct SendParam {\\n uint32 dstEid; // Destination endpoint ID.\\n bytes32 to; // Recipient address.\\n uint256 amountLD; // Amount to send in local decimals.\\n uint256 minAmountLD; // Minimum amount to send in local decimals.\\n bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message.\\n bytes composeMsg; // The composed message for the send() operation.\\n bytes oftCmd; // The OFT command to be executed, unused in default OFT implementations.\\n}\\n\\n/**\\n * @dev Struct representing OFT limit information.\\n * @dev These amounts can change dynamically and are up the specific oft implementation.\\n */\\nstruct OFTLimit {\\n uint256 minAmountLD; // Minimum amount in local decimals that can be sent to the recipient.\\n uint256 maxAmountLD; // Maximum amount in local decimals that can be sent to the recipient.\\n}\\n\\n/**\\n * @dev Struct representing OFT receipt information.\\n */\\nstruct OFTReceipt {\\n uint256 amountSentLD; // Amount of tokens ACTUALLY debited from the sender in local decimals.\\n // @dev In non-default implementations, the amountReceivedLD COULD differ from this value.\\n uint256 amountReceivedLD; // Amount of tokens to be received on the remote side.\\n}\\n\\n/**\\n * @dev Struct representing OFT fee details.\\n * @dev Future proof mechanism to provide a standardized way to communicate fees to things like a UI.\\n */\\nstruct OFTFeeDetail {\\n int256 feeAmountLD; // Amount of the fee in local decimals.\\n string description; // Description of the fee.\\n}\\n\\n/**\\n * @title IOFT\\n * @dev Interface for the OftChain (OFT) token.\\n * @dev Does not inherit ERC20 to accommodate usage by OFTAdapter as well.\\n * @dev This specific interface ID is '0x02e49c2c'.\\n */\\ninterface IOFT {\\n // Custom error messages\\n error InvalidLocalDecimals();\\n error SlippageExceeded(uint256 amountLD, uint256 minAmountLD);\\n error AmountSDOverflowed(uint256 amountSD);\\n\\n // Events\\n event OFTSent(\\n bytes32 indexed guid, // GUID of the OFT message.\\n uint32 dstEid, // Destination Endpoint ID.\\n address indexed fromAddress, // Address of the sender on the src chain.\\n uint256 amountSentLD, // Amount of tokens sent in local decimals.\\n uint256 amountReceivedLD // Amount of tokens received in local decimals.\\n );\\n event OFTReceived(\\n bytes32 indexed guid, // GUID of the OFT message.\\n uint32 srcEid, // Source Endpoint ID.\\n address indexed toAddress, // Address of the recipient on the dst chain.\\n uint256 amountReceivedLD // Amount of tokens received in local decimals.\\n );\\n\\n /**\\n * @notice Retrieves interfaceID and the version of the OFT.\\n * @return interfaceId The interface ID.\\n * @return version The version.\\n *\\n * @dev interfaceId: This specific interface ID is '0x02e49c2c'.\\n * @dev version: Indicates a cross-chain compatible msg encoding with other OFTs.\\n * @dev If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented.\\n * ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)\\n */\\n function oftVersion() external view returns (bytes4 interfaceId, uint64 version);\\n\\n /**\\n * @notice Retrieves the address of the token associated with the OFT.\\n * @return token The address of the ERC20 token implementation.\\n */\\n function token() external view returns (address);\\n\\n /**\\n * @notice Indicates whether the OFT contract requires approval of the 'token()' to send.\\n * @return requiresApproval Needs approval of the underlying token implementation.\\n *\\n * @dev Allows things like wallet implementers to determine integration requirements,\\n * without understanding the underlying token implementation.\\n */\\n function approvalRequired() external view returns (bool);\\n\\n /**\\n * @notice Retrieves the shared decimals of the OFT.\\n * @return sharedDecimals The shared decimals of the OFT.\\n */\\n function sharedDecimals() external view returns (uint8);\\n\\n /**\\n * @notice Provides the fee breakdown and settings data for an OFT. Unused in the default implementation.\\n * @param _sendParam The parameters for the send operation.\\n * @return limit The OFT limit information.\\n * @return oftFeeDetails The details of OFT fees.\\n * @return receipt The OFT receipt information.\\n */\\n function quoteOFT(\\n SendParam calldata _sendParam\\n ) external view returns (OFTLimit memory, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory);\\n\\n /**\\n * @notice Provides a quote for the send() operation.\\n * @param _sendParam The parameters for the send() operation.\\n * @param _payInLzToken Flag indicating whether the caller is paying in the LZ token.\\n * @return fee The calculated LayerZero messaging fee from the send() operation.\\n *\\n * @dev MessagingFee: LayerZero msg fee\\n * - nativeFee: The native fee.\\n * - lzTokenFee: The lzToken fee.\\n */\\n function quoteSend(SendParam calldata _sendParam, bool _payInLzToken) external view returns (MessagingFee memory);\\n\\n /**\\n * @notice Executes the send() operation.\\n * @param _sendParam The parameters for the send operation.\\n * @param _fee The fee information supplied by the caller.\\n * - nativeFee: The native fee.\\n * - lzTokenFee: The lzToken fee.\\n * @param _refundAddress The address to receive any excess funds from fees etc. on the src.\\n * @return receipt The LayerZero messaging receipt from the send() operation.\\n * @return oftReceipt The OFT receipt information.\\n *\\n * @dev MessagingReceipt: LayerZero msg receipt\\n * - guid: The unique identifier for the sent message.\\n * - nonce: The nonce of the sent message.\\n * - fee: The LayerZero fee incurred for the message.\\n */\\n function send(\\n SendParam calldata _sendParam,\\n MessagingFee calldata _fee,\\n address _refundAddress\\n ) external payable returns (MessagingReceipt memory, OFTReceipt memory);\\n}\\n\",\"keccak256\":\"0xc60c7b4374b3d89f33b8de982f463c92374a8548800c816fe776f0ec76351fb0\",\"license\":\"MIT\"},\"@layerzerolabs/oft-evm/contracts/libs/OFTComposeMsgCodec.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nlibrary OFTComposeMsgCodec {\\n // Offset constants for decoding composed messages\\n uint8 private constant NONCE_OFFSET = 8;\\n uint8 private constant SRC_EID_OFFSET = 12;\\n uint8 private constant AMOUNT_LD_OFFSET = 44;\\n uint8 private constant COMPOSE_FROM_OFFSET = 76;\\n\\n /**\\n * @dev Encodes a OFT composed message.\\n * @param _nonce The nonce value.\\n * @param _srcEid The source endpoint ID.\\n * @param _amountLD The amount in local decimals.\\n * @param _composeMsg The composed message.\\n * @return _msg The encoded Composed message.\\n */\\n function encode(\\n uint64 _nonce,\\n uint32 _srcEid,\\n uint256 _amountLD,\\n bytes memory _composeMsg // 0x[composeFrom][composeMsg]\\n ) internal pure returns (bytes memory _msg) {\\n _msg = abi.encodePacked(_nonce, _srcEid, _amountLD, _composeMsg);\\n }\\n\\n /**\\n * @dev Retrieves the nonce for the composed message.\\n * @param _msg The message.\\n * @return The nonce value.\\n */\\n function nonce(bytes calldata _msg) internal pure returns (uint64) {\\n return uint64(bytes8(_msg[:NONCE_OFFSET]));\\n }\\n\\n /**\\n * @dev Retrieves the source endpoint ID for the composed message.\\n * @param _msg The message.\\n * @return The source endpoint ID.\\n */\\n function srcEid(bytes calldata _msg) internal pure returns (uint32) {\\n return uint32(bytes4(_msg[NONCE_OFFSET:SRC_EID_OFFSET]));\\n }\\n\\n /**\\n * @dev Retrieves the amount in local decimals from the composed message.\\n * @param _msg The message.\\n * @return The amount in local decimals.\\n */\\n function amountLD(bytes calldata _msg) internal pure returns (uint256) {\\n return uint256(bytes32(_msg[SRC_EID_OFFSET:AMOUNT_LD_OFFSET]));\\n }\\n\\n /**\\n * @dev Retrieves the composeFrom value from the composed message.\\n * @param _msg The message.\\n * @return The composeFrom value.\\n */\\n function composeFrom(bytes calldata _msg) internal pure returns (bytes32) {\\n return bytes32(_msg[AMOUNT_LD_OFFSET:COMPOSE_FROM_OFFSET]);\\n }\\n\\n /**\\n * @dev Retrieves the composed message.\\n * @param _msg The message.\\n * @return The composed message.\\n */\\n function composeMsg(bytes calldata _msg) internal pure returns (bytes memory) {\\n return _msg[COMPOSE_FROM_OFFSET:];\\n }\\n\\n /**\\n * @dev Converts an address to bytes32.\\n * @param _addr The address to convert.\\n * @return The bytes32 representation of the address.\\n */\\n function addressToBytes32(address _addr) internal pure returns (bytes32) {\\n return bytes32(uint256(uint160(_addr)));\\n }\\n\\n /**\\n * @dev Converts bytes32 to an address.\\n * @param _b The bytes32 value to convert.\\n * @return The address representation of bytes32.\\n */\\n function bytes32ToAddress(bytes32 _b) internal pure returns (address) {\\n return address(uint160(uint256(_b)));\\n }\\n}\\n\",\"keccak256\":\"0xaae73d6eb8b9561c43f1802f3c416c00ccd35f172b711f9781ccdf1b25a40db5\",\"license\":\"MIT\"},\"@layerzerolabs/oft-evm/contracts/libs/OFTMsgCodec.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nlibrary OFTMsgCodec {\\n // Offset constants for encoding and decoding OFT messages\\n uint8 private constant SEND_TO_OFFSET = 32;\\n uint8 private constant SEND_AMOUNT_SD_OFFSET = 40;\\n\\n /**\\n * @dev Encodes an OFT LayerZero message.\\n * @param _sendTo The recipient address.\\n * @param _amountShared The amount in shared decimals.\\n * @param _composeMsg The composed message.\\n * @return _msg The encoded message.\\n * @return hasCompose A boolean indicating whether the message has a composed payload.\\n */\\n function encode(\\n bytes32 _sendTo,\\n uint64 _amountShared,\\n bytes memory _composeMsg\\n ) internal view returns (bytes memory _msg, bool hasCompose) {\\n hasCompose = _composeMsg.length > 0;\\n // @dev Remote chains will want to know the composed function caller ie. msg.sender on the src.\\n _msg = hasCompose\\n ? abi.encodePacked(_sendTo, _amountShared, addressToBytes32(msg.sender), _composeMsg)\\n : abi.encodePacked(_sendTo, _amountShared);\\n }\\n\\n /**\\n * @dev Checks if the OFT message is composed.\\n * @param _msg The OFT message.\\n * @return A boolean indicating whether the message is composed.\\n */\\n function isComposed(bytes calldata _msg) internal pure returns (bool) {\\n return _msg.length > SEND_AMOUNT_SD_OFFSET;\\n }\\n\\n /**\\n * @dev Retrieves the recipient address from the OFT message.\\n * @param _msg The OFT message.\\n * @return The recipient address.\\n */\\n function sendTo(bytes calldata _msg) internal pure returns (bytes32) {\\n return bytes32(_msg[:SEND_TO_OFFSET]);\\n }\\n\\n /**\\n * @dev Retrieves the amount in shared decimals from the OFT message.\\n * @param _msg The OFT message.\\n * @return The amount in shared decimals.\\n */\\n function amountSD(bytes calldata _msg) internal pure returns (uint64) {\\n return uint64(bytes8(_msg[SEND_TO_OFFSET:SEND_AMOUNT_SD_OFFSET]));\\n }\\n\\n /**\\n * @dev Retrieves the composed message from the OFT message.\\n * @param _msg The OFT message.\\n * @return The composed message.\\n */\\n function composeMsg(bytes calldata _msg) internal pure returns (bytes memory) {\\n return _msg[SEND_AMOUNT_SD_OFFSET:];\\n }\\n\\n /**\\n * @dev Converts an address to bytes32.\\n * @param _addr The address to convert.\\n * @return The bytes32 representation of the address.\\n */\\n function addressToBytes32(address _addr) internal pure returns (bytes32) {\\n return bytes32(uint256(uint160(_addr)));\\n }\\n\\n /**\\n * @dev Converts bytes32 to an address.\\n * @param _b The bytes32 value to convert.\\n * @return The address representation of bytes32.\\n */\\n function bytes32ToAddress(bytes32 _b) internal pure returns (address) {\\n return address(uint160(uint256(_b)));\\n }\\n}\\n\",\"keccak256\":\"0x5358948017669c03e157f871d8c38e988f9004dbd0801ad3119d2487f0d40b0b\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/Ownable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\\n\\npragma solidity ^0.8.20;\\n\\nimport {Context} from \\\"../utils/Context.sol\\\";\\n\\n/**\\n * @dev Contract module which provides a basic access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * The initial owner is set to the address provided by the deployer. This can\\n * later be changed with {transferOwnership}.\\n *\\n * This module is used through inheritance. It will make available the modifier\\n * `onlyOwner`, which can be applied to your functions to restrict their use to\\n * the owner.\\n */\\nabstract contract Ownable is Context {\\n address private _owner;\\n\\n /**\\n * @dev The caller account is not authorized to perform an operation.\\n */\\n error OwnableUnauthorizedAccount(address account);\\n\\n /**\\n * @dev The owner is not a valid owner account. (eg. `address(0)`)\\n */\\n error OwnableInvalidOwner(address owner);\\n\\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\\n\\n /**\\n * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\\n */\\n constructor(address initialOwner) {\\n if (initialOwner == address(0)) {\\n revert OwnableInvalidOwner(address(0));\\n }\\n _transferOwnership(initialOwner);\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the owner.\\n */\\n modifier onlyOwner() {\\n _checkOwner();\\n _;\\n }\\n\\n /**\\n * @dev Returns the address of the current owner.\\n */\\n function owner() public view virtual returns (address) {\\n return _owner;\\n }\\n\\n /**\\n * @dev Throws if the sender is not the owner.\\n */\\n function _checkOwner() internal view virtual {\\n if (owner() != _msgSender()) {\\n revert OwnableUnauthorizedAccount(_msgSender());\\n }\\n }\\n\\n /**\\n * @dev Leaves the contract without owner. It will not be possible to call\\n * `onlyOwner` functions. Can only be called by the current owner.\\n *\\n * NOTE: Renouncing ownership will leave the contract without an owner,\\n * thereby disabling any functionality that is only available to the owner.\\n */\\n function renounceOwnership() public virtual onlyOwner {\\n _transferOwnership(address(0));\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual onlyOwner {\\n if (newOwner == address(0)) {\\n revert OwnableInvalidOwner(address(0));\\n }\\n _transferOwnership(newOwner);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Internal function without access restriction.\\n */\\n function _transferOwnership(address newOwner) internal virtual {\\n address oldOwner = _owner;\\n _owner = newOwner;\\n emit OwnershipTransferred(oldOwner, newOwner);\\n }\\n}\\n\",\"keccak256\":\"0xff6d0bb2e285473e5311d9d3caacb525ae3538a80758c10649a4d61029b017bb\",\"license\":\"MIT\"},\"@openzeppelin/contracts/interfaces/IERC1363.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1363.sol)\\n\\npragma solidity >=0.6.2;\\n\\nimport {IERC20} from \\\"./IERC20.sol\\\";\\nimport {IERC165} from \\\"./IERC165.sol\\\";\\n\\n/**\\n * @title IERC1363\\n * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].\\n *\\n * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract\\n * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.\\n */\\ninterface IERC1363 is IERC20, IERC165 {\\n /*\\n * Note: the ERC-165 identifier for this interface is 0xb0202a11.\\n * 0xb0202a11 ===\\n * bytes4(keccak256('transferAndCall(address,uint256)')) ^\\n * bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^\\n * bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^\\n * bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^\\n * bytes4(keccak256('approveAndCall(address,uint256)')) ^\\n * bytes4(keccak256('approveAndCall(address,uint256,bytes)'))\\n */\\n\\n /**\\n * @dev Moves a `value` amount of tokens from the caller's account to `to`\\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\\n * @param to The address which you want to transfer to.\\n * @param value The amount of tokens to be transferred.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function transferAndCall(address to, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from the caller's account to `to`\\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\\n * @param to The address which you want to transfer to.\\n * @param value The amount of tokens to be transferred.\\n * @param data Additional data with no specified format, sent in call to `to`.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism\\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\\n * @param from The address which you want to send tokens from.\\n * @param to The address which you want to transfer to.\\n * @param value The amount of tokens to be transferred.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function transferFromAndCall(address from, address to, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism\\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\\n * @param from The address which you want to send tokens from.\\n * @param to The address which you want to transfer to.\\n * @param value The amount of tokens to be transferred.\\n * @param data Additional data with no specified format, sent in call to `to`.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);\\n\\n /**\\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\\n * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.\\n * @param spender The address which will spend the funds.\\n * @param value The amount of tokens to be spent.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function approveAndCall(address spender, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\\n * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.\\n * @param spender The address which will spend the funds.\\n * @param value The amount of tokens to be spent.\\n * @param data Additional data with no specified format, sent in call to `spender`.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);\\n}\\n\",\"keccak256\":\"0xd5ea07362ab630a6a3dee4285a74cf2377044ca2e4be472755ad64d7c5d4b69d\",\"license\":\"MIT\"},\"@openzeppelin/contracts/interfaces/IERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC165.sol)\\n\\npragma solidity >=0.4.16;\\n\\nimport {IERC165} from \\\"../utils/introspection/IERC165.sol\\\";\\n\",\"keccak256\":\"0x0afcb7e740d1537b252cb2676f600465ce6938398569f09ba1b9ca240dde2dfc\",\"license\":\"MIT\"},\"@openzeppelin/contracts/interfaces/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC20.sol)\\n\\npragma solidity >=0.4.16;\\n\\nimport {IERC20} from \\\"../token/ERC20/IERC20.sol\\\";\\n\",\"keccak256\":\"0x1a6221315ce0307746c2c4827c125d821ee796c74a676787762f4778671d4f44\",\"license\":\"MIT\"},\"@openzeppelin/contracts/interfaces/draft-IERC6093.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.5.0) (interfaces/draft-IERC6093.sol)\\n\\npragma solidity >=0.8.4;\\n\\n/**\\n * @dev Standard ERC-20 Errors\\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-20 tokens.\\n */\\ninterface IERC20Errors {\\n /**\\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n * @param balance Current balance for the interacting account.\\n * @param needed Minimum amount required to perform a transfer.\\n */\\n error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);\\n\\n /**\\n * @dev Indicates a failure with the token `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n */\\n error ERC20InvalidSender(address sender);\\n\\n /**\\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\\n * @param receiver Address to which tokens are being transferred.\\n */\\n error ERC20InvalidReceiver(address receiver);\\n\\n /**\\n * @dev Indicates a failure with the `spender`\\u2019s `allowance`. Used in transfers.\\n * @param spender Address that may be allowed to operate on tokens without being their owner.\\n * @param allowance Amount of tokens a `spender` is allowed to operate with.\\n * @param needed Minimum amount required to perform a transfer.\\n */\\n error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);\\n\\n /**\\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\\n * @param approver Address initiating an approval operation.\\n */\\n error ERC20InvalidApprover(address approver);\\n\\n /**\\n * @dev Indicates a failure with the `spender` to be approved. Used in approvals.\\n * @param spender Address that may be allowed to operate on tokens without being their owner.\\n */\\n error ERC20InvalidSpender(address spender);\\n}\\n\\n/**\\n * @dev Standard ERC-721 Errors\\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-721 tokens.\\n */\\ninterface IERC721Errors {\\n /**\\n * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-721.\\n * Used in balance queries.\\n * @param owner Address of the current owner of a token.\\n */\\n error ERC721InvalidOwner(address owner);\\n\\n /**\\n * @dev Indicates a `tokenId` whose `owner` is the zero address.\\n * @param tokenId Identifier number of a token.\\n */\\n error ERC721NonexistentToken(uint256 tokenId);\\n\\n /**\\n * @dev Indicates an error related to the ownership over a particular token. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n * @param tokenId Identifier number of a token.\\n * @param owner Address of the current owner of a token.\\n */\\n error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);\\n\\n /**\\n * @dev Indicates a failure with the token `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n */\\n error ERC721InvalidSender(address sender);\\n\\n /**\\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\\n * @param receiver Address to which tokens are being transferred.\\n */\\n error ERC721InvalidReceiver(address receiver);\\n\\n /**\\n * @dev Indicates a failure with the `operator`\\u2019s approval. Used in transfers.\\n * @param operator Address that may be allowed to operate on tokens without being their owner.\\n * @param tokenId Identifier number of a token.\\n */\\n error ERC721InsufficientApproval(address operator, uint256 tokenId);\\n\\n /**\\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\\n * @param approver Address initiating an approval operation.\\n */\\n error ERC721InvalidApprover(address approver);\\n\\n /**\\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\\n * @param operator Address that may be allowed to operate on tokens without being their owner.\\n */\\n error ERC721InvalidOperator(address operator);\\n}\\n\\n/**\\n * @dev Standard ERC-1155 Errors\\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-1155 tokens.\\n */\\ninterface IERC1155Errors {\\n /**\\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n * @param balance Current balance for the interacting account.\\n * @param needed Minimum amount required to perform a transfer.\\n * @param tokenId Identifier number of a token.\\n */\\n error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);\\n\\n /**\\n * @dev Indicates a failure with the token `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n */\\n error ERC1155InvalidSender(address sender);\\n\\n /**\\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\\n * @param receiver Address to which tokens are being transferred.\\n */\\n error ERC1155InvalidReceiver(address receiver);\\n\\n /**\\n * @dev Indicates a failure with the `operator`\\u2019s approval. Used in transfers.\\n * @param operator Address that may be allowed to operate on tokens without being their owner.\\n * @param owner Address of the current owner of a token.\\n */\\n error ERC1155MissingApprovalForAll(address operator, address owner);\\n\\n /**\\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\\n * @param approver Address initiating an approval operation.\\n */\\n error ERC1155InvalidApprover(address approver);\\n\\n /**\\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\\n * @param operator Address that may be allowed to operate on tokens without being their owner.\\n */\\n error ERC1155InvalidOperator(address operator);\\n\\n /**\\n * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.\\n * Used in batch transfers.\\n * @param idsLength Length of the array of token identifiers\\n * @param valuesLength Length of the array of token amounts\\n */\\n error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);\\n}\\n\",\"keccak256\":\"0x1b88b3fb3d85ba5496d7d5f396f83ee1fddcdd6762059ff65992655b67920998\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/ERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.5.0) (token/ERC20/ERC20.sol)\\n\\npragma solidity ^0.8.20;\\n\\nimport {IERC20} from \\\"./IERC20.sol\\\";\\nimport {IERC20Metadata} from \\\"./extensions/IERC20Metadata.sol\\\";\\nimport {Context} from \\\"../../utils/Context.sol\\\";\\nimport {IERC20Errors} from \\\"../../interfaces/draft-IERC6093.sol\\\";\\n\\n/**\\n * @dev Implementation of the {IERC20} interface.\\n *\\n * This implementation is agnostic to the way tokens are created. This means\\n * that a supply mechanism has to be added in a derived contract using {_mint}.\\n *\\n * TIP: For a detailed writeup see our guide\\n * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How\\n * to implement supply mechanisms].\\n *\\n * The default value of {decimals} is 18. To change this, you should override\\n * this function so it returns a different value.\\n *\\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\\n * instead returning `false` on failure. This behavior is nonetheless\\n * conventional and does not conflict with the expectations of ERC-20\\n * applications.\\n */\\nabstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {\\n mapping(address account => uint256) private _balances;\\n\\n mapping(address account => mapping(address spender => uint256)) private _allowances;\\n\\n uint256 private _totalSupply;\\n\\n string private _name;\\n string private _symbol;\\n\\n /**\\n * @dev Sets the values for {name} and {symbol}.\\n *\\n * Both values are immutable: they can only be set once during construction.\\n */\\n constructor(string memory name_, string memory symbol_) {\\n _name = name_;\\n _symbol = symbol_;\\n }\\n\\n /**\\n * @dev Returns the name of the token.\\n */\\n function name() public view virtual returns (string memory) {\\n return _name;\\n }\\n\\n /**\\n * @dev Returns the symbol of the token, usually a shorter version of the\\n * name.\\n */\\n function symbol() public view virtual returns (string memory) {\\n return _symbol;\\n }\\n\\n /**\\n * @dev Returns the number of decimals used to get its user representation.\\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\\n *\\n * Tokens usually opt for a value of 18, imitating the relationship between\\n * Ether and Wei. This is the default value returned by this function, unless\\n * it's overridden.\\n *\\n * NOTE: This information is only used for _display_ purposes: it in\\n * no way affects any of the arithmetic of the contract, including\\n * {IERC20-balanceOf} and {IERC20-transfer}.\\n */\\n function decimals() public view virtual returns (uint8) {\\n return 18;\\n }\\n\\n /// @inheritdoc IERC20\\n function totalSupply() public view virtual returns (uint256) {\\n return _totalSupply;\\n }\\n\\n /// @inheritdoc IERC20\\n function balanceOf(address account) public view virtual returns (uint256) {\\n return _balances[account];\\n }\\n\\n /**\\n * @dev See {IERC20-transfer}.\\n *\\n * Requirements:\\n *\\n * - `to` cannot be the zero address.\\n * - the caller must have a balance of at least `value`.\\n */\\n function transfer(address to, uint256 value) public virtual returns (bool) {\\n address owner = _msgSender();\\n _transfer(owner, to, value);\\n return true;\\n }\\n\\n /// @inheritdoc IERC20\\n function allowance(address owner, address spender) public view virtual returns (uint256) {\\n return _allowances[owner][spender];\\n }\\n\\n /**\\n * @dev See {IERC20-approve}.\\n *\\n * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on\\n * `transferFrom`. This is semantically equivalent to an infinite approval.\\n *\\n * Requirements:\\n *\\n * - `spender` cannot be the zero address.\\n */\\n function approve(address spender, uint256 value) public virtual returns (bool) {\\n address owner = _msgSender();\\n _approve(owner, spender, value);\\n return true;\\n }\\n\\n /**\\n * @dev See {IERC20-transferFrom}.\\n *\\n * Skips emitting an {Approval} event indicating an allowance update. This is not\\n * required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].\\n *\\n * NOTE: Does not update the allowance if the current allowance\\n * is the maximum `uint256`.\\n *\\n * Requirements:\\n *\\n * - `from` and `to` cannot be the zero address.\\n * - `from` must have a balance of at least `value`.\\n * - the caller must have allowance for ``from``'s tokens of at least\\n * `value`.\\n */\\n function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {\\n address spender = _msgSender();\\n _spendAllowance(from, spender, value);\\n _transfer(from, to, value);\\n return true;\\n }\\n\\n /**\\n * @dev Moves a `value` amount of tokens from `from` to `to`.\\n *\\n * This internal function is equivalent to {transfer}, and can be used to\\n * e.g. implement automatic token fees, slashing mechanisms, etc.\\n *\\n * Emits a {Transfer} event.\\n *\\n * NOTE: This function is not virtual, {_update} should be overridden instead.\\n */\\n function _transfer(address from, address to, uint256 value) internal {\\n if (from == address(0)) {\\n revert ERC20InvalidSender(address(0));\\n }\\n if (to == address(0)) {\\n revert ERC20InvalidReceiver(address(0));\\n }\\n _update(from, to, value);\\n }\\n\\n /**\\n * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`\\n * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding\\n * this function.\\n *\\n * Emits a {Transfer} event.\\n */\\n function _update(address from, address to, uint256 value) internal virtual {\\n if (from == address(0)) {\\n // Overflow check required: The rest of the code assumes that totalSupply never overflows\\n _totalSupply += value;\\n } else {\\n uint256 fromBalance = _balances[from];\\n if (fromBalance < value) {\\n revert ERC20InsufficientBalance(from, fromBalance, value);\\n }\\n unchecked {\\n // Overflow not possible: value <= fromBalance <= totalSupply.\\n _balances[from] = fromBalance - value;\\n }\\n }\\n\\n if (to == address(0)) {\\n unchecked {\\n // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.\\n _totalSupply -= value;\\n }\\n } else {\\n unchecked {\\n // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.\\n _balances[to] += value;\\n }\\n }\\n\\n emit Transfer(from, to, value);\\n }\\n\\n /**\\n * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).\\n * Relies on the `_update` mechanism\\n *\\n * Emits a {Transfer} event with `from` set to the zero address.\\n *\\n * NOTE: This function is not virtual, {_update} should be overridden instead.\\n */\\n function _mint(address account, uint256 value) internal {\\n if (account == address(0)) {\\n revert ERC20InvalidReceiver(address(0));\\n }\\n _update(address(0), account, value);\\n }\\n\\n /**\\n * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.\\n * Relies on the `_update` mechanism.\\n *\\n * Emits a {Transfer} event with `to` set to the zero address.\\n *\\n * NOTE: This function is not virtual, {_update} should be overridden instead\\n */\\n function _burn(address account, uint256 value) internal {\\n if (account == address(0)) {\\n revert ERC20InvalidSender(address(0));\\n }\\n _update(account, address(0), value);\\n }\\n\\n /**\\n * @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.\\n *\\n * This internal function is equivalent to `approve`, and can be used to\\n * e.g. set automatic allowances for certain subsystems, etc.\\n *\\n * Emits an {Approval} event.\\n *\\n * Requirements:\\n *\\n * - `owner` cannot be the zero address.\\n * - `spender` cannot be the zero address.\\n *\\n * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.\\n */\\n function _approve(address owner, address spender, uint256 value) internal {\\n _approve(owner, spender, value, true);\\n }\\n\\n /**\\n * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.\\n *\\n * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by\\n * `_spendAllowance` during the `transferFrom` operation sets the flag to false. This saves gas by not emitting any\\n * `Approval` event during `transferFrom` operations.\\n *\\n * Anyone who wishes to continue emitting `Approval` events on the `transferFrom` operation can force the flag to\\n * true using the following override:\\n *\\n * ```solidity\\n * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {\\n * super._approve(owner, spender, value, true);\\n * }\\n * ```\\n *\\n * Requirements are the same as {_approve}.\\n */\\n function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {\\n if (owner == address(0)) {\\n revert ERC20InvalidApprover(address(0));\\n }\\n if (spender == address(0)) {\\n revert ERC20InvalidSpender(address(0));\\n }\\n _allowances[owner][spender] = value;\\n if (emitEvent) {\\n emit Approval(owner, spender, value);\\n }\\n }\\n\\n /**\\n * @dev Updates `owner`'s allowance for `spender` based on spent `value`.\\n *\\n * Does not update the allowance value in case of infinite allowance.\\n * Revert if not enough allowance is available.\\n *\\n * Does not emit an {Approval} event.\\n */\\n function _spendAllowance(address owner, address spender, uint256 value) internal virtual {\\n uint256 currentAllowance = allowance(owner, spender);\\n if (currentAllowance < type(uint256).max) {\\n if (currentAllowance < value) {\\n revert ERC20InsufficientAllowance(spender, currentAllowance, value);\\n }\\n unchecked {\\n _approve(owner, spender, currentAllowance - value, false);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x669464167428061ee0f8618b73b3ee90aff8405683e7ddde8cd77dadaa1afe29\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)\\n\\npragma solidity >=0.4.16;\\n\\n/**\\n * @dev Interface of the ERC-20 standard as defined in the ERC.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n\\n /**\\n * @dev Returns the value of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the value of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from the caller's account to `to`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address to, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\\n * caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from `from` to `to` using the\\n * allowance mechanism. `value` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(address from, address to, uint256 value) external returns (bool);\\n}\\n\",\"keccak256\":\"0x74ed01eb66b923d0d0cfe3be84604ac04b76482a55f9dd655e1ef4d367f95bc2\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/extensions/IERC20Metadata.sol)\\n\\npragma solidity >=0.6.2;\\n\\nimport {IERC20} from \\\"../IERC20.sol\\\";\\n\\n/**\\n * @dev Interface for the optional metadata functions from the ERC-20 standard.\\n */\\ninterface IERC20Metadata is IERC20 {\\n /**\\n * @dev Returns the name of the token.\\n */\\n function name() external view returns (string memory);\\n\\n /**\\n * @dev Returns the symbol of the token.\\n */\\n function symbol() external view returns (string memory);\\n\\n /**\\n * @dev Returns the decimals places of the token.\\n */\\n function decimals() external view returns (uint8);\\n}\\n\",\"keccak256\":\"0xd6fa4088198f04eef10c5bce8a2f4d60554b7ec4b987f684393c01bf79b94d9f\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.5.0) (token/ERC20/utils/SafeERC20.sol)\\n\\npragma solidity ^0.8.20;\\n\\nimport {IERC20} from \\\"../IERC20.sol\\\";\\nimport {IERC1363} from \\\"../../../interfaces/IERC1363.sol\\\";\\n\\n/**\\n * @title SafeERC20\\n * @dev Wrappers around ERC-20 operations that throw on failure (when the token\\n * contract returns false). Tokens that return no value (and instead revert or\\n * throw on failure) are also supported, non-reverting calls are assumed to be\\n * successful.\\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\\n */\\nlibrary SafeERC20 {\\n /**\\n * @dev An operation with an ERC-20 token failed.\\n */\\n error SafeERC20FailedOperation(address token);\\n\\n /**\\n * @dev Indicates a failed `decreaseAllowance` request.\\n */\\n error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);\\n\\n /**\\n * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful.\\n */\\n function safeTransfer(IERC20 token, address to, uint256 value) internal {\\n if (!_safeTransfer(token, to, value, true)) {\\n revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the\\n * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.\\n */\\n function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {\\n if (!_safeTransferFrom(token, from, to, value, true)) {\\n revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.\\n */\\n function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {\\n return _safeTransfer(token, to, value, false);\\n }\\n\\n /**\\n * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.\\n */\\n function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {\\n return _safeTransferFrom(token, from, to, value, false);\\n }\\n\\n /**\\n * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful.\\n *\\n * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the \\\"client\\\"\\n * smart contract uses ERC-7674 to set temporary allowances, then the \\\"client\\\" smart contract should avoid using\\n * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract\\n * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.\\n */\\n function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {\\n uint256 oldAllowance = token.allowance(address(this), spender);\\n forceApprove(token, spender, oldAllowance + value);\\n }\\n\\n /**\\n * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no\\n * value, non-reverting calls are assumed to be successful.\\n *\\n * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the \\\"client\\\"\\n * smart contract uses ERC-7674 to set temporary allowances, then the \\\"client\\\" smart contract should avoid using\\n * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract\\n * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.\\n */\\n function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {\\n unchecked {\\n uint256 currentAllowance = token.allowance(address(this), spender);\\n if (currentAllowance < requestedDecrease) {\\n revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);\\n }\\n forceApprove(token, spender, currentAllowance - requestedDecrease);\\n }\\n }\\n\\n /**\\n * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval\\n * to be set to zero before setting it to a non-zero value, such as USDT.\\n *\\n * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function\\n * only sets the \\\"standard\\\" allowance. Any temporary allowance will remain active, in addition to the value being\\n * set here.\\n */\\n function forceApprove(IERC20 token, address spender, uint256 value) internal {\\n if (!_safeApprove(token, spender, value, false)) {\\n if (!_safeApprove(token, spender, 0, true)) revert SafeERC20FailedOperation(address(token));\\n if (!_safeApprove(token, spender, value, true)) revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no\\n * code. This can be used to implement an {ERC721}-like safe transfer that relies on {ERC1363} checks when\\n * targeting contracts.\\n *\\n * Reverts if the returned value is other than `true`.\\n */\\n function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {\\n if (to.code.length == 0) {\\n safeTransfer(token, to, value);\\n } else if (!token.transferAndCall(to, value, data)) {\\n revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target\\n * has no code. This can be used to implement an {ERC721}-like safe transfer that relies on {ERC1363} checks when\\n * targeting contracts.\\n *\\n * Reverts if the returned value is other than `true`.\\n */\\n function transferFromAndCallRelaxed(\\n IERC1363 token,\\n address from,\\n address to,\\n uint256 value,\\n bytes memory data\\n ) internal {\\n if (to.code.length == 0) {\\n safeTransferFrom(token, from, to, value);\\n } else if (!token.transferFromAndCall(from, to, value, data)) {\\n revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no\\n * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when\\n * targeting contracts.\\n *\\n * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.\\n * Oppositely, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}\\n * once without retrying, and relies on the returned value to be true.\\n *\\n * Reverts if the returned value is other than `true`.\\n */\\n function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {\\n if (to.code.length == 0) {\\n forceApprove(token, to, value);\\n } else if (!token.approveAndCall(to, value, data)) {\\n revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Imitates a Solidity `token.transfer(to, value)` call, relaxing the requirement on the return value: the\\n * return value is optional (but if data is returned, it must not be false).\\n *\\n * @param token The token targeted by the call.\\n * @param to The recipient of the tokens\\n * @param value The amount of token to transfer\\n * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.\\n */\\n function _safeTransfer(IERC20 token, address to, uint256 value, bool bubble) private returns (bool success) {\\n bytes4 selector = IERC20.transfer.selector;\\n\\n assembly (\\\"memory-safe\\\") {\\n let fmp := mload(0x40)\\n mstore(0x00, selector)\\n mstore(0x04, and(to, shr(96, not(0))))\\n mstore(0x24, value)\\n success := call(gas(), token, 0, 0x00, 0x44, 0x00, 0x20)\\n // if call success and return is true, all is good.\\n // otherwise (not success or return is not true), we need to perform further checks\\n if iszero(and(success, eq(mload(0x00), 1))) {\\n // if the call was a failure and bubble is enabled, bubble the error\\n if and(iszero(success), bubble) {\\n returndatacopy(fmp, 0x00, returndatasize())\\n revert(fmp, returndatasize())\\n }\\n // if the return value is not true, then the call is only successful if:\\n // - the token address has code\\n // - the returndata is empty\\n success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))\\n }\\n mstore(0x40, fmp)\\n }\\n }\\n\\n /**\\n * @dev Imitates a Solidity `token.transferFrom(from, to, value)` call, relaxing the requirement on the return\\n * value: the return value is optional (but if data is returned, it must not be false).\\n *\\n * @param token The token targeted by the call.\\n * @param from The sender of the tokens\\n * @param to The recipient of the tokens\\n * @param value The amount of token to transfer\\n * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.\\n */\\n function _safeTransferFrom(\\n IERC20 token,\\n address from,\\n address to,\\n uint256 value,\\n bool bubble\\n ) private returns (bool success) {\\n bytes4 selector = IERC20.transferFrom.selector;\\n\\n assembly (\\\"memory-safe\\\") {\\n let fmp := mload(0x40)\\n mstore(0x00, selector)\\n mstore(0x04, and(from, shr(96, not(0))))\\n mstore(0x24, and(to, shr(96, not(0))))\\n mstore(0x44, value)\\n success := call(gas(), token, 0, 0x00, 0x64, 0x00, 0x20)\\n // if call success and return is true, all is good.\\n // otherwise (not success or return is not true), we need to perform further checks\\n if iszero(and(success, eq(mload(0x00), 1))) {\\n // if the call was a failure and bubble is enabled, bubble the error\\n if and(iszero(success), bubble) {\\n returndatacopy(fmp, 0x00, returndatasize())\\n revert(fmp, returndatasize())\\n }\\n // if the return value is not true, then the call is only successful if:\\n // - the token address has code\\n // - the returndata is empty\\n success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))\\n }\\n mstore(0x40, fmp)\\n mstore(0x60, 0)\\n }\\n }\\n\\n /**\\n * @dev Imitates a Solidity `token.approve(spender, value)` call, relaxing the requirement on the return value:\\n * the return value is optional (but if data is returned, it must not be false).\\n *\\n * @param token The token targeted by the call.\\n * @param spender The spender of the tokens\\n * @param value The amount of token to transfer\\n * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.\\n */\\n function _safeApprove(IERC20 token, address spender, uint256 value, bool bubble) private returns (bool success) {\\n bytes4 selector = IERC20.approve.selector;\\n\\n assembly (\\\"memory-safe\\\") {\\n let fmp := mload(0x40)\\n mstore(0x00, selector)\\n mstore(0x04, and(spender, shr(96, not(0))))\\n mstore(0x24, value)\\n success := call(gas(), token, 0, 0x00, 0x44, 0x00, 0x20)\\n // if call success and return is true, all is good.\\n // otherwise (not success or return is not true), we need to perform further checks\\n if iszero(and(success, eq(mload(0x00), 1))) {\\n // if the call was a failure and bubble is enabled, bubble the error\\n if and(iszero(success), bubble) {\\n returndatacopy(fmp, 0x00, returndatasize())\\n revert(fmp, returndatasize())\\n }\\n // if the return value is not true, then the call is only successful if:\\n // - the token address has code\\n // - the returndata is empty\\n success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))\\n }\\n mstore(0x40, fmp)\\n }\\n }\\n}\\n\",\"keccak256\":\"0x304d732678032a9781ae85c8f204c8fba3d3a5e31c02616964e75cfdc5049098\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Context.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\\n\\npragma solidity ^0.8.20;\\n\\n/**\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract Context {\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n\\n function _contextSuffixLength() internal view virtual returns (uint256) {\\n return 0;\\n }\\n}\\n\",\"keccak256\":\"0x493033a8d1b176a037b2cc6a04dad01a5c157722049bbecf632ca876224dd4b2\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/introspection/IERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)\\n\\npragma solidity >=0.4.16;\\n\\n/**\\n * @dev Interface of the ERC-165 standard, as defined in the\\n * https://eips.ethereum.org/EIPS/eip-165[ERC].\\n *\\n * Implementers can declare support of contract interfaces, which can then be\\n * queried by others ({ERC165Checker}).\\n *\\n * For an implementation, see {ERC165}.\\n */\\ninterface IERC165 {\\n /**\\n * @dev Returns true if this contract implements the interface defined by\\n * `interfaceId`. See the corresponding\\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]\\n * to learn more about how these ids are created.\\n *\\n * This function call must use less than 30 000 gas.\\n */\\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x8891738ffe910f0cf2da09566928589bf5d63f4524dd734fd9cedbac3274dd5c\",\"license\":\"MIT\"},\"contracts/MyOFT.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\r\\npragma solidity ^0.8.22;\\r\\n\\r\\nimport { Ownable } from \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\r\\nimport { OFT } from \\\"@layerzerolabs/oft-evm/contracts/OFT.sol\\\";\\r\\n\\r\\ncontract MyOFT is OFT {\\r\\n constructor(\\r\\n string memory _name,\\r\\n string memory _symbol,\\r\\n address _lzEndpoint,\\r\\n address _delegate\\r\\n ) OFT(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) {}\\r\\n\\r\\n /// @notice Open mint, TESTNET ONLY \\u2014 same as ToyOFT, so the demo tasks work against either.\\r\\n /// @dev Deliberately unguarded: the demo mints to itself before each send, and a testnet OFT\\r\\n /// with no way to obtain tokens cannot be used to exercise the DVN at all. Do NOT ship\\r\\n /// this to a network where the token has value.\\r\\n /// `virtual` because MyOFTMock declares the same function for the hardhat tests.\\r\\n function mint(address _to, uint256 _amount) public virtual {\\r\\n _mint(_to, _amount);\\r\\n }\\r\\n}\\r\\n\",\"keccak256\":\"0x742ac02bacb2a1fa397bf560593b446ea96b15f7031123129b0fa6bcbd4c0e80\",\"license\":\"UNLICENSED\"}},\"version\":1}", + "bytecode": "0x60c06040523480156200001157600080fd5b5060405162003790380380620037908339810160408190526200003491620002d2565b83838383838360128484818181818d6001600160a01b0381166200007257604051631e4fbdf760e01b81526000600482015260240160405180910390fd5b6200007d8162000198565b506001600160a01b038083166080528116620000ac57604051632d618d8160e21b815260040160405180910390fd5b60805160405163ca5eb5e160e01b81526001600160a01b0383811660048301529091169063ca5eb5e190602401600060405180830381600087803b158015620000f457600080fd5b505af115801562000109573d6000803e3d6000fd5b505050505050505062000121620001e860201b60201c565b60ff168360ff16101562000148576040516301e9714b60e41b815260040160405180910390fd5b6200015560068462000377565b6200016290600a62000496565b60a052506008915062000178905083826200053f565b5060096200018782826200053f565b50505050505050505050506200060b565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b600690565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126200021557600080fd5b81516001600160401b0380821115620002325762000232620001ed565b604051601f8301601f19908116603f011681019082821181831017156200025d576200025d620001ed565b81604052838152602092508660208588010111156200027b57600080fd5b600091505b838210156200029f578582018301518183018401529082019062000280565b6000602085830101528094505050505092915050565b80516001600160a01b0381168114620002cd57600080fd5b919050565b60008060008060808587031215620002e957600080fd5b84516001600160401b03808211156200030157600080fd5b6200030f8883890162000203565b955060208701519150808211156200032657600080fd5b50620003358782880162000203565b9350506200034660408601620002b5565b91506200035660608601620002b5565b905092959194509250565b634e487b7160e01b600052601160045260246000fd5b60ff828116828216039081111562000393576200039362000361565b92915050565b600181815b80851115620003da578160001904821115620003be57620003be62000361565b80851615620003cc57918102915b93841c93908002906200039e565b509250929050565b600082620003f35750600162000393565b81620004025750600062000393565b81600181146200041b5760028114620004265762000446565b600191505062000393565b60ff8411156200043a576200043a62000361565b50506001821b62000393565b5060208310610133831016604e8410600b84101617156200046b575081810a62000393565b62000477838362000399565b80600019048211156200048e576200048e62000361565b029392505050565b6000620004a760ff841683620003e2565b9392505050565b600181811c90821680620004c357607f821691505b602082108103620004e457634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200053a576000816000526020600020601f850160051c81016020861015620005155750805b601f850160051c820191505b81811015620005365782815560010162000521565b5050505b505050565b81516001600160401b038111156200055b576200055b620001ed565b62000573816200056c8454620004ae565b84620004ea565b602080601f831160018114620005ab5760008415620005925750858301515b600019600386901b1c1916600185901b17855562000536565b600085815260208120601f198616915b82811015620005dc57888601518255948401946001909101908401620005bb565b5085821015620005fb5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60805160a051613119620006776000396000818161064901528181611b0c01528181611b810152611d8601526000818161050801528181610a78015281816110d201528181611349015281816116b401528181611eab01528181611fe5015261209e01526131196000f3fe60806040526004361061025c5760003560e01c8063715018a611610144578063bb0b6a53116100b6578063d045a0dc1161007a578063d045a0dc14610780578063d424388514610793578063dd62ed3e146107b3578063f2fde38b146107f9578063fc0c546a1461048c578063ff7bd03d1461081957600080fd5b8063bb0b6a53146106df578063bc70b3541461070c578063bd815db01461072c578063c7c7f5b31461073f578063ca5eb5e11461076057600080fd5b806395d89b411161010857806395d89b4114610622578063963efcaa146106375780639f68b9641461066b578063a9059cbb1461067f578063b731ea0a1461069f578063b98bd070146106bf57600080fd5b8063715018a6146105805780637d25a05e1461059557806382413eac146105d0578063857749b0146105f05780638da5cb5b1461060457600080fd5b806323b872dd116101dd57806352ae2879116101a157806352ae28791461048c5780635535d4611461049f5780635a0dfe4d146104bf5780635e280f11146104f65780636fc1b31e1461052a57806370a082311461054a57600080fd5b806323b872dd146103dd578063313ce567146103fd5780633400288b1461041f5780633b6f743b1461043f57806340c10f191461046c57600080fd5b8063134d4f2511610224578063134d4f2514610338578063156a0d0f1461036057806317442b701461038757806318160ddd146103a95780631f5e1334146103c857600080fd5b806306fdde0314610261578063095ea7b31461028c5780630d35b415146102bc578063111ecdad146102eb57806313137d6514610323575b600080fd5b34801561026d57600080fd5b50610276610839565b60405161028391906121fd565b60405180910390f35b34801561029857600080fd5b506102ac6102a7366004612225565b6108cb565b6040519015158152602001610283565b3480156102c857600080fd5b506102dc6102d7366004612269565b6108e5565b6040516102839392919061229d565b3480156102f757600080fd5b5060045461030b906001600160a01b031681565b6040516001600160a01b039091168152602001610283565b610336610331366004612390565b610a76565b005b34801561034457600080fd5b5061034d600281565b60405161ffff9091168152602001610283565b34801561036c57600080fd5b506040805162b9270b60e21b81526001602082015201610283565b34801561039357600080fd5b5060408051600181526002602082015201610283565b3480156103b557600080fd5b506007545b604051908152602001610283565b3480156103d457600080fd5b5061034d600181565b3480156103e957600080fd5b506102ac6103f836600461242f565b610b36565b34801561040957600080fd5b5060125b60405160ff9091168152602001610283565b34801561042b57600080fd5b5061033661043a366004612489565b610b5c565b34801561044b57600080fd5b5061045f61045a3660046124b3565b610b72565b6040516102839190612504565b34801561047857600080fd5b50610336610487366004612225565b610bd9565b34801561049857600080fd5b503061030b565b3480156104ab57600080fd5b506102766104ba36600461252d565b610be3565b3480156104cb57600080fd5b506102ac6104da366004612489565b63ffffffff919091166000908152600160205260409020541490565b34801561050257600080fd5b5061030b7f000000000000000000000000000000000000000000000000000000000000000081565b34801561053657600080fd5b50610336610545366004612560565b610c88565b34801561055657600080fd5b506103ba610565366004612560565b6001600160a01b031660009081526005602052604090205490565b34801561058c57600080fd5b50610336610ce5565b3480156105a157600080fd5b506105b86105b0366004612489565b600092915050565b6040516001600160401b039091168152602001610283565b3480156105dc57600080fd5b506102ac6105eb36600461257d565b610cf9565b3480156105fc57600080fd5b50600661040d565b34801561061057600080fd5b506000546001600160a01b031661030b565b34801561062e57600080fd5b50610276610d0e565b34801561064357600080fd5b506103ba7f000000000000000000000000000000000000000000000000000000000000000081565b34801561067757600080fd5b5060006102ac565b34801561068b57600080fd5b506102ac61069a366004612225565b610d1d565b3480156106ab57600080fd5b5060025461030b906001600160a01b031681565b3480156106cb57600080fd5b506103366106da366004612627565b610d2b565b3480156106eb57600080fd5b506103ba6106fa366004612668565b60016020526000908152604090205481565b34801561071857600080fd5b50610276610727366004612683565b610d45565b61033661073a366004612627565b610eed565b61075261074d3660046126e3565b611077565b604051610283929190612750565b34801561076c57600080fd5b5061033661077b366004612560565b6110ab565b61033661078e366004612390565b611131565b34801561079f57600080fd5b506103366107ae366004612560565b611160565b3480156107bf57600080fd5b506103ba6107ce3660046127a2565b6001600160a01b03918216600090815260066020908152604080832093909416825291909152205490565b34801561080557600080fd5b50610336610814366004612560565b6111b6565b34801561082557600080fd5b506102ac6108343660046127d0565b6111f4565b606060088054610848906127ec565b80601f0160208091040260200160405190810160405280929190818152602001828054610874906127ec565b80156108c15780601f10610896576101008083540402835291602001916108c1565b820191906000526020600020905b8154815290600101906020018083116108a457829003601f168201915b5050505050905090565b6000336108d981858561122a565b60019150505b92915050565b60408051808201909152600080825260208201526060610918604051806040016040528060008152602001600081525090565b600080306001600160a01b031663fc0c546a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610959573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061097d9190612820565b6001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109ba573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109de919061283d565b60408051808201825284815260208082018490528251600080825291810190935290975091925090610a33565b604080518082019091526000815260606020820152815260200190600190039081610a0b5790505b509350600080610a58604089013560608a0135610a5360208c018c612668565b61123c565b60408051808201909152918252602082015296989597505050505050565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163314610ac6576040516391ac5e4f60e01b81523360048201526024015b60405180910390fd5b60208701803590610ae090610adb908a612668565b611278565b14610b1e57610af26020880188612668565b60405163309afaf360e21b815263ffffffff909116600482015260208801356024820152604401610abd565b610b2d878787878787876112b4565b50505050505050565b600033610b4485828561141b565b610b4f85858561149a565b60019150505b9392505050565b610b646114f9565b610b6e8282611526565b5050565b60408051808201909152600080825260208201526000610ba260408501356060860135610a536020880188612668565b915050600080610bb2868461157b565b9092509050610bcf610bc76020880188612668565b83838861169e565b9695505050505050565b610b6e828261177f565b600360209081526000928352604080842090915290825290208054610c07906127ec565b80601f0160208091040260200160405190810160405280929190818152602001828054610c33906127ec565b8015610c805780601f10610c5557610100808354040283529160200191610c80565b820191906000526020600020905b815481529060010190602001808311610c6357829003601f168201915b505050505081565b610c906114f9565b600480546001600160a01b0319166001600160a01b0383169081179091556040519081527ff0be4f1e87349231d80c36b33f9e8639658eeaf474014dee15a3e6a4d4414197906020015b60405180910390a150565b610ced6114f9565b610cf760006117b5565b565b6001600160a01b03811630145b949350505050565b606060098054610848906127ec565b6000336108d981858561149a565b610d336114f9565b610b6e610d40828461290d565b611805565b63ffffffff8416600090815260036020908152604080832061ffff87168452909152812080546060929190610d79906127ec565b80601f0160208091040260200160405190810160405280929190818152602001828054610da5906127ec565b8015610df25780601f10610dc757610100808354040283529160200191610df2565b820191906000526020600020905b815481529060010190602001808311610dd557829003601f168201915b505050505090508051600003610e425783838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929450610d069350505050565b6000839003610e52579050610d06565b60028310610ed057610e9984848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061190c92505050565b80610ea78460028188612a22565b604051602001610eb993929190612a4c565b604051602081830303815290604052915050610d06565b8383604051639a6d49cd60e01b8152600401610abd929190612a9d565b60005b81811015610ff65736838383818110610f0b57610f0b612ab1565b9050602002810190610f1d9190612ac7565b9050610f50610f2f6020830183612668565b602083013563ffffffff919091166000908152600160205260409020541490565b610f5a5750610fee565b3063d045a0dc60c08301358360a0810135610f79610100830183612ae8565b610f8a610100890160e08a01612560565b610f986101208a018a612ae8565b6040518963ffffffff1660e01b8152600401610fba9796959493929190612b43565b6000604051808303818588803b158015610fd357600080fd5b505af1158015610fe7573d6000803e3d6000fd5b5050505050505b600101610ef0565b50336001600160a01b0316638e9e70996040518163ffffffff1660e01b8152600401600060405180830381865afa158015611035573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261105d9190810190612bc9565b604051638351eea760e01b8152600401610abd91906121fd565b61107f612166565b604080518082019091526000808252602082015261109e858585611938565b915091505b935093915050565b6110b36114f9565b60405163ca5eb5e160e01b81526001600160a01b0382811660048301527f0000000000000000000000000000000000000000000000000000000000000000169063ca5eb5e190602401600060405180830381600087803b15801561111657600080fd5b505af115801561112a573d6000803e3d6000fd5b5050505050565b3330146111515760405163029a949d60e31b815260040160405180910390fd5b610b2d87878787878787610b1e565b6111686114f9565b600280546001600160a01b0319166001600160a01b0383169081179091556040519081527fd48d879cef83a1c0bdda516f27b13ddb1b3f8bbac1c9e1511bb2a659c242776090602001610cda565b6111be6114f9565b6001600160a01b0381166111e857604051631e4fbdf760e01b815260006004820152602401610abd565b6111f1816117b5565b50565b600060208201803590600190839061120c9086612668565b63ffffffff1681526020810191909152604001600020541492915050565b6112378383836001611a33565b505050565b60008061124885611b08565b9150819050838110156110a3576040516371c4efed60e01b81526004810182905260248101859052604401610abd565b63ffffffff8116600090815260016020526040812054806108df5760405163f6ff4fb760e01b815263ffffffff84166004820152602401610abd565b60006112c66112c38787611b3f565b90565b905060006112f2826112e06112db8a8a611b57565b611b7a565b6112ed60208d018d612668565b611baf565b905060288611156113b957600061132f61131260608c0160408d01612c36565b61131f60208d018d612668565b8461132a8c8c611bd7565b611c22565b604051633e5ac80960e11b81529091506001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690637cb59012906113859086908d906000908790600401612c53565b600060405180830381600087803b15801561139f57600080fd5b505af11580156113b3573d6000803e3d6000fd5b50505050505b6001600160a01b038216887fefed6d3500546b29533b128a29e3a94d70788727f0507505ac12eaf2e578fd9c6113f260208d018d612668565b6040805163ffffffff9092168252602082018690520160405180910390a3505050505050505050565b6001600160a01b03838116600090815260066020908152604080832093861683529290522054600019811015611494578181101561148557604051637dc7a0d960e11b81526001600160a01b03841660048201526024810182905260448101839052606401610abd565b61149484848484036000611a33565b50505050565b6001600160a01b0383166114c457604051634b637e8f60e11b815260006004820152602401610abd565b6001600160a01b0382166114ee5760405163ec442f0560e01b815260006004820152602401610abd565b611237838383611c54565b6000546001600160a01b03163314610cf75760405163118cdaa760e01b8152336004820152602401610abd565b63ffffffff8216600081815260016020908152604091829020849055815192835282018390527f238399d427b947898edb290f5ff0f9109849b1c3ba196a42e35f00c50a54b98b910160405180910390a15050565b60608060006115d8856020013561159186611d7e565b61159e60a0890189612ae8565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611dd892505050565b90935090506000816115eb5760016115ee565b60025b905061160e6116006020880188612668565b8261072760808a018a612ae8565b6004549093506001600160a01b031680156116945760405163043a78eb60e01b81526001600160a01b0382169063043a78eb906116519088908890600401612c84565b602060405180830381865afa15801561166e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116929190612ca9565b505b5050509250929050565b60408051808201909152600080825260208201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663ddc28c586040518060a001604052808863ffffffff16815260200161170189611278565b8152602001878152602001868152602001851515815250306040518363ffffffff1660e01b8152600401611736929190612cc6565b6040805180830381865afa158015611752573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117769190612d6f565b95945050505050565b6001600160a01b0382166117a95760405163ec442f0560e01b815260006004820152602401610abd565b610b6e60008383611c54565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60005b81518110156118dc5761183782828151811061182657611826612ab1565b60200260200101516040015161190c565b81818151811061184957611849612ab1565b6020026020010151604001516003600084848151811061186b5761186b612ab1565b60200260200101516000015163ffffffff1663ffffffff16815260200190815260200160002060008484815181106118a5576118a5612ab1565b60200260200101516020015161ffff1661ffff16815260200190815260200160002090816118d39190612ddb565b50600101611808565b507fbe4864a8e820971c0247f5992e2da559595f7bf076a21cb5928d443d2a13b67481604051610cda9190612e9a565b600281015161ffff8116600314610b6e5781604051639a6d49cd60e01b8152600401610abd91906121fd565b611940612166565b604080518082019091526000808252602082015260008061197733604089013560608a013561197260208c018c612668565b611e52565b91509150600080611988898461157b565b90925090506119b461199d60208b018b612668565b83836119ae368d90038d018d612f25565b8b611e78565b60408051808201909152858152602080820186905282519298509096503391907f85496b760a4b7f8d66384b9df21b381f5d1b1e79f229a47aaf4c232edc2fe59a90611a02908d018d612668565b6040805163ffffffff909216825260208201899052810187905260600160405180910390a350505050935093915050565b6001600160a01b038416611a5d5760405163e602df0560e01b815260006004820152602401610abd565b6001600160a01b038316611a8757604051634a1406b160e11b815260006004820152602401610abd565b6001600160a01b038085166000908152600660209081526040808320938716835292905220829055801561149457826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92584604051611afa91815260200190565b60405180910390a350505050565b60007f0000000000000000000000000000000000000000000000000000000000000000611b358184612f6d565b6108df9190612f8f565b6000611b4e6020828486612a22565b610b5591612fa6565b6000611b67602860208486612a22565b611b7091612fc4565b60c01c9392505050565b60006108df7f00000000000000000000000000000000000000000000000000000000000000006001600160401b038416612f8f565b60006001600160a01b038416611bc55761dead93505b611bcf848461177f565b509092915050565b6060611be68260288186612a22565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929695505050505050565b606084848484604051602001611c3b9493929190612ff4565b6040516020818303038152906040529050949350505050565b6001600160a01b038316611c7f578060076000828254611c749190613043565b90915550611cf19050565b6001600160a01b03831660009081526005602052604090205481811015611cd25760405163391434e360e21b81526001600160a01b03851660048201526024810182905260448101839052606401610abd565b6001600160a01b03841660009081526005602052604090209082900390555b6001600160a01b038216611d0d57600780548290039055611d2c565b6001600160a01b03821660009081526005602052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051611d7191815260200190565b60405180910390a3505050565b600080611dab7f000000000000000000000000000000000000000000000000000000000000000084612f6d565b90506001600160401b038111156108df5760405163e2ce941360e01b815260048101829052602401610abd565b8051606090151580611e21578484604051602001611e0d92919091825260c01b6001600160c01b031916602082015260280190565b604051602081830303815290604052611e48565b84843385604051602001611e389493929190613056565b6040516020818303038152906040525b9150935093915050565b600080611e6085858561123c565b9092509050611e6f8683611f83565b94509492505050565b611e80612166565b6000611e8f8460000151611fb9565b602085015190915015611ea957611ea98460200151611fe1565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316632637a450826040518060a001604052808b63ffffffff168152602001611ef98c611278565b81526020018a815260200189815260200160008960200151111515815250866040518463ffffffff1660e01b8152600401611f35929190612cc6565b60806040518083038185885af1158015611f53573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190611f789190613099565b979650505050505050565b6001600160a01b038216611fad57604051634b637e8f60e11b815260006004820152602401610abd565b610b6e82600083611c54565b6000813414611fdd576040516304fb820960e51b8152346004820152602401610abd565b5090565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663e4fe1d946040518163ffffffff1660e01b8152600401602060405180830381865afa158015612041573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120659190612820565b90506001600160a01b03811661208e576040516329b99a9560e11b815260040160405180910390fd5b610b6e6001600160a01b038216337f0000000000000000000000000000000000000000000000000000000000000000856120cc8484848460016120f4565b61149457604051635274afe760e01b81526001600160a01b0385166004820152602401610abd565b6040516323b872dd60e01b60008181526001600160a01b038781166004528616602452604485905291602083606481808c5af192506001600051148316612154578383151615612147573d6000823e3d81fd5b6000883b113d1516831692505b60405250600060605295945050505050565b60405180606001604052806000801916815260200160006001600160401b031681526020016121a8604051806040016040528060008152602001600081525090565b905290565b60005b838110156121c85781810151838201526020016121b0565b50506000910152565b600081518084526121e98160208601602086016121ad565b601f01601f19169290920160200192915050565b602081526000610b5560208301846121d1565b6001600160a01b03811681146111f157600080fd5b6000806040838503121561223857600080fd5b823561224381612210565b946020939093013593505050565b600060e0828403121561226357600080fd5b50919050565b60006020828403121561227b57600080fd5b81356001600160401b0381111561229157600080fd5b610d0684828501612251565b8351815260208085015190820152600060a08201604060a0604085015281865180845260c08601915060c08160051b8701019350602080890160005b838110156123185788870360bf19018552815180518852830151838801879052612305878901826121d1565b97505093820193908201906001016122d9565b50508751606088015250505060208501516080850152509050610d06565b60006060828403121561226357600080fd5b60008083601f84011261235a57600080fd5b5081356001600160401b0381111561237157600080fd5b60208301915083602082850101111561238957600080fd5b9250929050565b600080600080600080600060e0888a0312156123ab57600080fd5b6123b58989612336565b96506060880135955060808801356001600160401b03808211156123d857600080fd5b6123e48b838c01612348565b909750955060a08a013591506123f982612210565b90935060c0890135908082111561240f57600080fd5b5061241c8a828b01612348565b989b979a50959850939692959293505050565b60008060006060848603121561244457600080fd5b833561244f81612210565b9250602084013561245f81612210565b929592945050506040919091013590565b803563ffffffff8116811461248457600080fd5b919050565b6000806040838503121561249c57600080fd5b61224383612470565b80151581146111f157600080fd5b600080604083850312156124c657600080fd5b82356001600160401b038111156124dc57600080fd5b6124e885828601612251565b92505060208301356124f9816124a5565b809150509250929050565b8151815260208083015190820152604081016108df565b803561ffff8116811461248457600080fd5b6000806040838503121561254057600080fd5b61254983612470565b91506125576020840161251b565b90509250929050565b60006020828403121561257257600080fd5b8135610b5581612210565b60008060008060a0858703121561259357600080fd5b61259d8686612336565b935060608501356001600160401b038111156125b857600080fd5b6125c487828801612348565b90945092505060808501356125d881612210565b939692955090935050565b60008083601f8401126125f557600080fd5b5081356001600160401b0381111561260c57600080fd5b6020830191508360208260051b850101111561238957600080fd5b6000806020838503121561263a57600080fd5b82356001600160401b0381111561265057600080fd5b61265c858286016125e3565b90969095509350505050565b60006020828403121561267a57600080fd5b610b5582612470565b6000806000806060858703121561269957600080fd5b6126a285612470565b93506126b06020860161251b565b925060408501356001600160401b038111156126cb57600080fd5b6126d787828801612348565b95989497509550505050565b600080600083850360808112156126f957600080fd5b84356001600160401b0381111561270f57600080fd5b61271b87828801612251565b9450506040601f198201121561273057600080fd5b50602084019150606084013561274581612210565b809150509250925092565b600060c082019050835182526001600160401b036020850151166020830152604084015161278b604084018280518252602090810151910152565b5082516080830152602083015160a0830152610b55565b600080604083850312156127b557600080fd5b82356127c081612210565b915060208301356124f981612210565b6000606082840312156127e257600080fd5b610b558383612336565b600181811c9082168061280057607f821691505b60208210810361226357634e487b7160e01b600052602260045260246000fd5b60006020828403121561283257600080fd5b8151610b5581612210565b60006020828403121561284f57600080fd5b5051919050565b634e487b7160e01b600052604160045260246000fd5b604051606081016001600160401b038111828210171561288e5761288e612856565b60405290565b604080519081016001600160401b038111828210171561288e5761288e612856565b604051601f8201601f191681016001600160401b03811182821017156128de576128de612856565b604052919050565b60006001600160401b038211156128ff576128ff612856565b50601f01601f191660200190565b60006001600160401b038084111561292757612927612856565b8360051b60206129388183016128b6565b86815291850191818101903684111561295057600080fd5b865b84811015612a165780358681111561296a5760008081fd5b8801606036829003121561297e5760008081fd5b61298661286c565b61298f82612470565b815261299c86830161251b565b86820152604080830135898111156129b45760008081fd5b929092019136601f8401126129c95760008081fd5b82356129dc6129d7826128e6565b6128b6565b81815236898387010111156129f15760008081fd5b818986018a830137600091810189019190915290820152845250918301918301612952565b50979650505050505050565b60008085851115612a3257600080fd5b83861115612a3f57600080fd5b5050820193919092039150565b60008451612a5e8184602089016121ad565b8201838582376000930192835250909392505050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b602081526000610d06602083018486612a74565b634e487b7160e01b600052603260045260246000fd5b6000823561013e19833603018112612ade57600080fd5b9190910192915050565b6000808335601e19843603018112612aff57600080fd5b8301803591506001600160401b03821115612b1957600080fd5b60200191503681900382131561238957600080fd5b6001600160401b03811681146111f157600080fd5b63ffffffff612b5189612470565b1681526020880135602082015260006040890135612b6e81612b2e565b6001600160401b03811660408401525087606083015260e06080830152612b9960e083018789612a74565b6001600160a01b03861660a084015282810360c0840152612bbb818587612a74565b9a9950505050505050505050565b600060208284031215612bdb57600080fd5b81516001600160401b03811115612bf157600080fd5b8201601f81018413612c0257600080fd5b8051612c106129d7826128e6565b818152856020838501011115612c2557600080fd5b6117768260208301602086016121ad565b600060208284031215612c4857600080fd5b8135610b5581612b2e565b60018060a01b038516815283602082015261ffff83166040820152608060608201526000610bcf60808301846121d1565b604081526000612c9760408301856121d1565b828103602084015261177681856121d1565b600060208284031215612cbb57600080fd5b8151610b55816124a5565b6040815263ffffffff8351166040820152602083015160608201526000604084015160a06080840152612cfc60e08401826121d1565b90506060850151603f198483030160a0850152612d1982826121d1565b60809690960151151560c08501525050506001600160a01b039190911660209091015290565b600060408284031215612d5157600080fd5b612d59612894565b9050815181526020820151602082015292915050565b600060408284031215612d8157600080fd5b610b558383612d3f565b601f821115611237576000816000526020600020601f850160051c81016020861015612db45750805b601f850160051c820191505b81811015612dd357828155600101612dc0565b505050505050565b81516001600160401b03811115612df457612df4612856565b612e0881612e0284546127ec565b84612d8b565b602080601f831160018114612e3d5760008415612e255750858301515b600019600386901b1c1916600185901b178555612dd3565b600085815260208120601f198616915b82811015612e6c57888601518255948401946001909101908401612e4d565b5085821015612e8a5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b600060208083018184528085518083526040925060408601915060408160051b87010184880160005b83811015612f1757888303603f190185528151805163ffffffff1684528781015161ffff16888501528601516060878501819052612f03818601836121d1565b968901969450505090860190600101612ec3565b509098975050505050505050565b600060408284031215612f3757600080fd5b612f3f612894565b82358152602083013560208201528091505092915050565b634e487b7160e01b600052601160045260246000fd5b600082612f8a57634e487b7160e01b600052601260045260246000fd5b500490565b80820281158282048414176108df576108df612f57565b803560208310156108df57600019602084900360031b1b1692915050565b6001600160c01b03198135818116916008851015612fec5780818660080360031b1b83161692505b505092915050565b6001600160401b0360c01b8560c01b16815263ffffffff60e01b8460e01b16600882015282600c8201526000825161303381602c8501602087016121ad565b91909101602c0195945050505050565b808201808211156108df576108df612f57565b8481526001600160401b0360c01b8460c01b166020820152826028820152600082516130898160488501602087016121ad565b9190910160480195945050505050565b6000608082840312156130ab57600080fd5b6130b361286c565b8251815260208301516130c581612b2e565b60208201526130d78460408501612d3f565b6040820152939250505056fea2646970667358221220aa404cddf55107bb4ef2c6beb0224c8f9f889ea8535030db0d36c362777d7cae64736f6c63430008160033", + "deployedBytecode": "0x60806040526004361061025c5760003560e01c8063715018a611610144578063bb0b6a53116100b6578063d045a0dc1161007a578063d045a0dc14610780578063d424388514610793578063dd62ed3e146107b3578063f2fde38b146107f9578063fc0c546a1461048c578063ff7bd03d1461081957600080fd5b8063bb0b6a53146106df578063bc70b3541461070c578063bd815db01461072c578063c7c7f5b31461073f578063ca5eb5e11461076057600080fd5b806395d89b411161010857806395d89b4114610622578063963efcaa146106375780639f68b9641461066b578063a9059cbb1461067f578063b731ea0a1461069f578063b98bd070146106bf57600080fd5b8063715018a6146105805780637d25a05e1461059557806382413eac146105d0578063857749b0146105f05780638da5cb5b1461060457600080fd5b806323b872dd116101dd57806352ae2879116101a157806352ae28791461048c5780635535d4611461049f5780635a0dfe4d146104bf5780635e280f11146104f65780636fc1b31e1461052a57806370a082311461054a57600080fd5b806323b872dd146103dd578063313ce567146103fd5780633400288b1461041f5780633b6f743b1461043f57806340c10f191461046c57600080fd5b8063134d4f2511610224578063134d4f2514610338578063156a0d0f1461036057806317442b701461038757806318160ddd146103a95780631f5e1334146103c857600080fd5b806306fdde0314610261578063095ea7b31461028c5780630d35b415146102bc578063111ecdad146102eb57806313137d6514610323575b600080fd5b34801561026d57600080fd5b50610276610839565b60405161028391906121fd565b60405180910390f35b34801561029857600080fd5b506102ac6102a7366004612225565b6108cb565b6040519015158152602001610283565b3480156102c857600080fd5b506102dc6102d7366004612269565b6108e5565b6040516102839392919061229d565b3480156102f757600080fd5b5060045461030b906001600160a01b031681565b6040516001600160a01b039091168152602001610283565b610336610331366004612390565b610a76565b005b34801561034457600080fd5b5061034d600281565b60405161ffff9091168152602001610283565b34801561036c57600080fd5b506040805162b9270b60e21b81526001602082015201610283565b34801561039357600080fd5b5060408051600181526002602082015201610283565b3480156103b557600080fd5b506007545b604051908152602001610283565b3480156103d457600080fd5b5061034d600181565b3480156103e957600080fd5b506102ac6103f836600461242f565b610b36565b34801561040957600080fd5b5060125b60405160ff9091168152602001610283565b34801561042b57600080fd5b5061033661043a366004612489565b610b5c565b34801561044b57600080fd5b5061045f61045a3660046124b3565b610b72565b6040516102839190612504565b34801561047857600080fd5b50610336610487366004612225565b610bd9565b34801561049857600080fd5b503061030b565b3480156104ab57600080fd5b506102766104ba36600461252d565b610be3565b3480156104cb57600080fd5b506102ac6104da366004612489565b63ffffffff919091166000908152600160205260409020541490565b34801561050257600080fd5b5061030b7f000000000000000000000000000000000000000000000000000000000000000081565b34801561053657600080fd5b50610336610545366004612560565b610c88565b34801561055657600080fd5b506103ba610565366004612560565b6001600160a01b031660009081526005602052604090205490565b34801561058c57600080fd5b50610336610ce5565b3480156105a157600080fd5b506105b86105b0366004612489565b600092915050565b6040516001600160401b039091168152602001610283565b3480156105dc57600080fd5b506102ac6105eb36600461257d565b610cf9565b3480156105fc57600080fd5b50600661040d565b34801561061057600080fd5b506000546001600160a01b031661030b565b34801561062e57600080fd5b50610276610d0e565b34801561064357600080fd5b506103ba7f000000000000000000000000000000000000000000000000000000000000000081565b34801561067757600080fd5b5060006102ac565b34801561068b57600080fd5b506102ac61069a366004612225565b610d1d565b3480156106ab57600080fd5b5060025461030b906001600160a01b031681565b3480156106cb57600080fd5b506103366106da366004612627565b610d2b565b3480156106eb57600080fd5b506103ba6106fa366004612668565b60016020526000908152604090205481565b34801561071857600080fd5b50610276610727366004612683565b610d45565b61033661073a366004612627565b610eed565b61075261074d3660046126e3565b611077565b604051610283929190612750565b34801561076c57600080fd5b5061033661077b366004612560565b6110ab565b61033661078e366004612390565b611131565b34801561079f57600080fd5b506103366107ae366004612560565b611160565b3480156107bf57600080fd5b506103ba6107ce3660046127a2565b6001600160a01b03918216600090815260066020908152604080832093909416825291909152205490565b34801561080557600080fd5b50610336610814366004612560565b6111b6565b34801561082557600080fd5b506102ac6108343660046127d0565b6111f4565b606060088054610848906127ec565b80601f0160208091040260200160405190810160405280929190818152602001828054610874906127ec565b80156108c15780601f10610896576101008083540402835291602001916108c1565b820191906000526020600020905b8154815290600101906020018083116108a457829003601f168201915b5050505050905090565b6000336108d981858561122a565b60019150505b92915050565b60408051808201909152600080825260208201526060610918604051806040016040528060008152602001600081525090565b600080306001600160a01b031663fc0c546a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610959573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061097d9190612820565b6001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109ba573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109de919061283d565b60408051808201825284815260208082018490528251600080825291810190935290975091925090610a33565b604080518082019091526000815260606020820152815260200190600190039081610a0b5790505b509350600080610a58604089013560608a0135610a5360208c018c612668565b61123c565b60408051808201909152918252602082015296989597505050505050565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163314610ac6576040516391ac5e4f60e01b81523360048201526024015b60405180910390fd5b60208701803590610ae090610adb908a612668565b611278565b14610b1e57610af26020880188612668565b60405163309afaf360e21b815263ffffffff909116600482015260208801356024820152604401610abd565b610b2d878787878787876112b4565b50505050505050565b600033610b4485828561141b565b610b4f85858561149a565b60019150505b9392505050565b610b646114f9565b610b6e8282611526565b5050565b60408051808201909152600080825260208201526000610ba260408501356060860135610a536020880188612668565b915050600080610bb2868461157b565b9092509050610bcf610bc76020880188612668565b83838861169e565b9695505050505050565b610b6e828261177f565b600360209081526000928352604080842090915290825290208054610c07906127ec565b80601f0160208091040260200160405190810160405280929190818152602001828054610c33906127ec565b8015610c805780601f10610c5557610100808354040283529160200191610c80565b820191906000526020600020905b815481529060010190602001808311610c6357829003601f168201915b505050505081565b610c906114f9565b600480546001600160a01b0319166001600160a01b0383169081179091556040519081527ff0be4f1e87349231d80c36b33f9e8639658eeaf474014dee15a3e6a4d4414197906020015b60405180910390a150565b610ced6114f9565b610cf760006117b5565b565b6001600160a01b03811630145b949350505050565b606060098054610848906127ec565b6000336108d981858561149a565b610d336114f9565b610b6e610d40828461290d565b611805565b63ffffffff8416600090815260036020908152604080832061ffff87168452909152812080546060929190610d79906127ec565b80601f0160208091040260200160405190810160405280929190818152602001828054610da5906127ec565b8015610df25780601f10610dc757610100808354040283529160200191610df2565b820191906000526020600020905b815481529060010190602001808311610dd557829003601f168201915b505050505090508051600003610e425783838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929450610d069350505050565b6000839003610e52579050610d06565b60028310610ed057610e9984848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061190c92505050565b80610ea78460028188612a22565b604051602001610eb993929190612a4c565b604051602081830303815290604052915050610d06565b8383604051639a6d49cd60e01b8152600401610abd929190612a9d565b60005b81811015610ff65736838383818110610f0b57610f0b612ab1565b9050602002810190610f1d9190612ac7565b9050610f50610f2f6020830183612668565b602083013563ffffffff919091166000908152600160205260409020541490565b610f5a5750610fee565b3063d045a0dc60c08301358360a0810135610f79610100830183612ae8565b610f8a610100890160e08a01612560565b610f986101208a018a612ae8565b6040518963ffffffff1660e01b8152600401610fba9796959493929190612b43565b6000604051808303818588803b158015610fd357600080fd5b505af1158015610fe7573d6000803e3d6000fd5b5050505050505b600101610ef0565b50336001600160a01b0316638e9e70996040518163ffffffff1660e01b8152600401600060405180830381865afa158015611035573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261105d9190810190612bc9565b604051638351eea760e01b8152600401610abd91906121fd565b61107f612166565b604080518082019091526000808252602082015261109e858585611938565b915091505b935093915050565b6110b36114f9565b60405163ca5eb5e160e01b81526001600160a01b0382811660048301527f0000000000000000000000000000000000000000000000000000000000000000169063ca5eb5e190602401600060405180830381600087803b15801561111657600080fd5b505af115801561112a573d6000803e3d6000fd5b5050505050565b3330146111515760405163029a949d60e31b815260040160405180910390fd5b610b2d87878787878787610b1e565b6111686114f9565b600280546001600160a01b0319166001600160a01b0383169081179091556040519081527fd48d879cef83a1c0bdda516f27b13ddb1b3f8bbac1c9e1511bb2a659c242776090602001610cda565b6111be6114f9565b6001600160a01b0381166111e857604051631e4fbdf760e01b815260006004820152602401610abd565b6111f1816117b5565b50565b600060208201803590600190839061120c9086612668565b63ffffffff1681526020810191909152604001600020541492915050565b6112378383836001611a33565b505050565b60008061124885611b08565b9150819050838110156110a3576040516371c4efed60e01b81526004810182905260248101859052604401610abd565b63ffffffff8116600090815260016020526040812054806108df5760405163f6ff4fb760e01b815263ffffffff84166004820152602401610abd565b60006112c66112c38787611b3f565b90565b905060006112f2826112e06112db8a8a611b57565b611b7a565b6112ed60208d018d612668565b611baf565b905060288611156113b957600061132f61131260608c0160408d01612c36565b61131f60208d018d612668565b8461132a8c8c611bd7565b611c22565b604051633e5ac80960e11b81529091506001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690637cb59012906113859086908d906000908790600401612c53565b600060405180830381600087803b15801561139f57600080fd5b505af11580156113b3573d6000803e3d6000fd5b50505050505b6001600160a01b038216887fefed6d3500546b29533b128a29e3a94d70788727f0507505ac12eaf2e578fd9c6113f260208d018d612668565b6040805163ffffffff9092168252602082018690520160405180910390a3505050505050505050565b6001600160a01b03838116600090815260066020908152604080832093861683529290522054600019811015611494578181101561148557604051637dc7a0d960e11b81526001600160a01b03841660048201526024810182905260448101839052606401610abd565b61149484848484036000611a33565b50505050565b6001600160a01b0383166114c457604051634b637e8f60e11b815260006004820152602401610abd565b6001600160a01b0382166114ee5760405163ec442f0560e01b815260006004820152602401610abd565b611237838383611c54565b6000546001600160a01b03163314610cf75760405163118cdaa760e01b8152336004820152602401610abd565b63ffffffff8216600081815260016020908152604091829020849055815192835282018390527f238399d427b947898edb290f5ff0f9109849b1c3ba196a42e35f00c50a54b98b910160405180910390a15050565b60608060006115d8856020013561159186611d7e565b61159e60a0890189612ae8565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611dd892505050565b90935090506000816115eb5760016115ee565b60025b905061160e6116006020880188612668565b8261072760808a018a612ae8565b6004549093506001600160a01b031680156116945760405163043a78eb60e01b81526001600160a01b0382169063043a78eb906116519088908890600401612c84565b602060405180830381865afa15801561166e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116929190612ca9565b505b5050509250929050565b60408051808201909152600080825260208201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663ddc28c586040518060a001604052808863ffffffff16815260200161170189611278565b8152602001878152602001868152602001851515815250306040518363ffffffff1660e01b8152600401611736929190612cc6565b6040805180830381865afa158015611752573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117769190612d6f565b95945050505050565b6001600160a01b0382166117a95760405163ec442f0560e01b815260006004820152602401610abd565b610b6e60008383611c54565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60005b81518110156118dc5761183782828151811061182657611826612ab1565b60200260200101516040015161190c565b81818151811061184957611849612ab1565b6020026020010151604001516003600084848151811061186b5761186b612ab1565b60200260200101516000015163ffffffff1663ffffffff16815260200190815260200160002060008484815181106118a5576118a5612ab1565b60200260200101516020015161ffff1661ffff16815260200190815260200160002090816118d39190612ddb565b50600101611808565b507fbe4864a8e820971c0247f5992e2da559595f7bf076a21cb5928d443d2a13b67481604051610cda9190612e9a565b600281015161ffff8116600314610b6e5781604051639a6d49cd60e01b8152600401610abd91906121fd565b611940612166565b604080518082019091526000808252602082015260008061197733604089013560608a013561197260208c018c612668565b611e52565b91509150600080611988898461157b565b90925090506119b461199d60208b018b612668565b83836119ae368d90038d018d612f25565b8b611e78565b60408051808201909152858152602080820186905282519298509096503391907f85496b760a4b7f8d66384b9df21b381f5d1b1e79f229a47aaf4c232edc2fe59a90611a02908d018d612668565b6040805163ffffffff909216825260208201899052810187905260600160405180910390a350505050935093915050565b6001600160a01b038416611a5d5760405163e602df0560e01b815260006004820152602401610abd565b6001600160a01b038316611a8757604051634a1406b160e11b815260006004820152602401610abd565b6001600160a01b038085166000908152600660209081526040808320938716835292905220829055801561149457826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92584604051611afa91815260200190565b60405180910390a350505050565b60007f0000000000000000000000000000000000000000000000000000000000000000611b358184612f6d565b6108df9190612f8f565b6000611b4e6020828486612a22565b610b5591612fa6565b6000611b67602860208486612a22565b611b7091612fc4565b60c01c9392505050565b60006108df7f00000000000000000000000000000000000000000000000000000000000000006001600160401b038416612f8f565b60006001600160a01b038416611bc55761dead93505b611bcf848461177f565b509092915050565b6060611be68260288186612a22565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929695505050505050565b606084848484604051602001611c3b9493929190612ff4565b6040516020818303038152906040529050949350505050565b6001600160a01b038316611c7f578060076000828254611c749190613043565b90915550611cf19050565b6001600160a01b03831660009081526005602052604090205481811015611cd25760405163391434e360e21b81526001600160a01b03851660048201526024810182905260448101839052606401610abd565b6001600160a01b03841660009081526005602052604090209082900390555b6001600160a01b038216611d0d57600780548290039055611d2c565b6001600160a01b03821660009081526005602052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051611d7191815260200190565b60405180910390a3505050565b600080611dab7f000000000000000000000000000000000000000000000000000000000000000084612f6d565b90506001600160401b038111156108df5760405163e2ce941360e01b815260048101829052602401610abd565b8051606090151580611e21578484604051602001611e0d92919091825260c01b6001600160c01b031916602082015260280190565b604051602081830303815290604052611e48565b84843385604051602001611e389493929190613056565b6040516020818303038152906040525b9150935093915050565b600080611e6085858561123c565b9092509050611e6f8683611f83565b94509492505050565b611e80612166565b6000611e8f8460000151611fb9565b602085015190915015611ea957611ea98460200151611fe1565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316632637a450826040518060a001604052808b63ffffffff168152602001611ef98c611278565b81526020018a815260200189815260200160008960200151111515815250866040518463ffffffff1660e01b8152600401611f35929190612cc6565b60806040518083038185885af1158015611f53573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190611f789190613099565b979650505050505050565b6001600160a01b038216611fad57604051634b637e8f60e11b815260006004820152602401610abd565b610b6e82600083611c54565b6000813414611fdd576040516304fb820960e51b8152346004820152602401610abd565b5090565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663e4fe1d946040518163ffffffff1660e01b8152600401602060405180830381865afa158015612041573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120659190612820565b90506001600160a01b03811661208e576040516329b99a9560e11b815260040160405180910390fd5b610b6e6001600160a01b038216337f0000000000000000000000000000000000000000000000000000000000000000856120cc8484848460016120f4565b61149457604051635274afe760e01b81526001600160a01b0385166004820152602401610abd565b6040516323b872dd60e01b60008181526001600160a01b038781166004528616602452604485905291602083606481808c5af192506001600051148316612154578383151615612147573d6000823e3d81fd5b6000883b113d1516831692505b60405250600060605295945050505050565b60405180606001604052806000801916815260200160006001600160401b031681526020016121a8604051806040016040528060008152602001600081525090565b905290565b60005b838110156121c85781810151838201526020016121b0565b50506000910152565b600081518084526121e98160208601602086016121ad565b601f01601f19169290920160200192915050565b602081526000610b5560208301846121d1565b6001600160a01b03811681146111f157600080fd5b6000806040838503121561223857600080fd5b823561224381612210565b946020939093013593505050565b600060e0828403121561226357600080fd5b50919050565b60006020828403121561227b57600080fd5b81356001600160401b0381111561229157600080fd5b610d0684828501612251565b8351815260208085015190820152600060a08201604060a0604085015281865180845260c08601915060c08160051b8701019350602080890160005b838110156123185788870360bf19018552815180518852830151838801879052612305878901826121d1565b97505093820193908201906001016122d9565b50508751606088015250505060208501516080850152509050610d06565b60006060828403121561226357600080fd5b60008083601f84011261235a57600080fd5b5081356001600160401b0381111561237157600080fd5b60208301915083602082850101111561238957600080fd5b9250929050565b600080600080600080600060e0888a0312156123ab57600080fd5b6123b58989612336565b96506060880135955060808801356001600160401b03808211156123d857600080fd5b6123e48b838c01612348565b909750955060a08a013591506123f982612210565b90935060c0890135908082111561240f57600080fd5b5061241c8a828b01612348565b989b979a50959850939692959293505050565b60008060006060848603121561244457600080fd5b833561244f81612210565b9250602084013561245f81612210565b929592945050506040919091013590565b803563ffffffff8116811461248457600080fd5b919050565b6000806040838503121561249c57600080fd5b61224383612470565b80151581146111f157600080fd5b600080604083850312156124c657600080fd5b82356001600160401b038111156124dc57600080fd5b6124e885828601612251565b92505060208301356124f9816124a5565b809150509250929050565b8151815260208083015190820152604081016108df565b803561ffff8116811461248457600080fd5b6000806040838503121561254057600080fd5b61254983612470565b91506125576020840161251b565b90509250929050565b60006020828403121561257257600080fd5b8135610b5581612210565b60008060008060a0858703121561259357600080fd5b61259d8686612336565b935060608501356001600160401b038111156125b857600080fd5b6125c487828801612348565b90945092505060808501356125d881612210565b939692955090935050565b60008083601f8401126125f557600080fd5b5081356001600160401b0381111561260c57600080fd5b6020830191508360208260051b850101111561238957600080fd5b6000806020838503121561263a57600080fd5b82356001600160401b0381111561265057600080fd5b61265c858286016125e3565b90969095509350505050565b60006020828403121561267a57600080fd5b610b5582612470565b6000806000806060858703121561269957600080fd5b6126a285612470565b93506126b06020860161251b565b925060408501356001600160401b038111156126cb57600080fd5b6126d787828801612348565b95989497509550505050565b600080600083850360808112156126f957600080fd5b84356001600160401b0381111561270f57600080fd5b61271b87828801612251565b9450506040601f198201121561273057600080fd5b50602084019150606084013561274581612210565b809150509250925092565b600060c082019050835182526001600160401b036020850151166020830152604084015161278b604084018280518252602090810151910152565b5082516080830152602083015160a0830152610b55565b600080604083850312156127b557600080fd5b82356127c081612210565b915060208301356124f981612210565b6000606082840312156127e257600080fd5b610b558383612336565b600181811c9082168061280057607f821691505b60208210810361226357634e487b7160e01b600052602260045260246000fd5b60006020828403121561283257600080fd5b8151610b5581612210565b60006020828403121561284f57600080fd5b5051919050565b634e487b7160e01b600052604160045260246000fd5b604051606081016001600160401b038111828210171561288e5761288e612856565b60405290565b604080519081016001600160401b038111828210171561288e5761288e612856565b604051601f8201601f191681016001600160401b03811182821017156128de576128de612856565b604052919050565b60006001600160401b038211156128ff576128ff612856565b50601f01601f191660200190565b60006001600160401b038084111561292757612927612856565b8360051b60206129388183016128b6565b86815291850191818101903684111561295057600080fd5b865b84811015612a165780358681111561296a5760008081fd5b8801606036829003121561297e5760008081fd5b61298661286c565b61298f82612470565b815261299c86830161251b565b86820152604080830135898111156129b45760008081fd5b929092019136601f8401126129c95760008081fd5b82356129dc6129d7826128e6565b6128b6565b81815236898387010111156129f15760008081fd5b818986018a830137600091810189019190915290820152845250918301918301612952565b50979650505050505050565b60008085851115612a3257600080fd5b83861115612a3f57600080fd5b5050820193919092039150565b60008451612a5e8184602089016121ad565b8201838582376000930192835250909392505050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b602081526000610d06602083018486612a74565b634e487b7160e01b600052603260045260246000fd5b6000823561013e19833603018112612ade57600080fd5b9190910192915050565b6000808335601e19843603018112612aff57600080fd5b8301803591506001600160401b03821115612b1957600080fd5b60200191503681900382131561238957600080fd5b6001600160401b03811681146111f157600080fd5b63ffffffff612b5189612470565b1681526020880135602082015260006040890135612b6e81612b2e565b6001600160401b03811660408401525087606083015260e06080830152612b9960e083018789612a74565b6001600160a01b03861660a084015282810360c0840152612bbb818587612a74565b9a9950505050505050505050565b600060208284031215612bdb57600080fd5b81516001600160401b03811115612bf157600080fd5b8201601f81018413612c0257600080fd5b8051612c106129d7826128e6565b818152856020838501011115612c2557600080fd5b6117768260208301602086016121ad565b600060208284031215612c4857600080fd5b8135610b5581612b2e565b60018060a01b038516815283602082015261ffff83166040820152608060608201526000610bcf60808301846121d1565b604081526000612c9760408301856121d1565b828103602084015261177681856121d1565b600060208284031215612cbb57600080fd5b8151610b55816124a5565b6040815263ffffffff8351166040820152602083015160608201526000604084015160a06080840152612cfc60e08401826121d1565b90506060850151603f198483030160a0850152612d1982826121d1565b60809690960151151560c08501525050506001600160a01b039190911660209091015290565b600060408284031215612d5157600080fd5b612d59612894565b9050815181526020820151602082015292915050565b600060408284031215612d8157600080fd5b610b558383612d3f565b601f821115611237576000816000526020600020601f850160051c81016020861015612db45750805b601f850160051c820191505b81811015612dd357828155600101612dc0565b505050505050565b81516001600160401b03811115612df457612df4612856565b612e0881612e0284546127ec565b84612d8b565b602080601f831160018114612e3d5760008415612e255750858301515b600019600386901b1c1916600185901b178555612dd3565b600085815260208120601f198616915b82811015612e6c57888601518255948401946001909101908401612e4d565b5085821015612e8a5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b600060208083018184528085518083526040925060408601915060408160051b87010184880160005b83811015612f1757888303603f190185528151805163ffffffff1684528781015161ffff16888501528601516060878501819052612f03818601836121d1565b968901969450505090860190600101612ec3565b509098975050505050505050565b600060408284031215612f3757600080fd5b612f3f612894565b82358152602083013560208201528091505092915050565b634e487b7160e01b600052601160045260246000fd5b600082612f8a57634e487b7160e01b600052601260045260246000fd5b500490565b80820281158282048414176108df576108df612f57565b803560208310156108df57600019602084900360031b1b1692915050565b6001600160c01b03198135818116916008851015612fec5780818660080360031b1b83161692505b505092915050565b6001600160401b0360c01b8560c01b16815263ffffffff60e01b8460e01b16600882015282600c8201526000825161303381602c8501602087016121ad565b91909101602c0195945050505050565b808201808211156108df576108df612f57565b8481526001600160401b0360c01b8460c01b166020820152826028820152600082516130898160488501602087016121ad565b9190910160480195945050505050565b6000608082840312156130ab57600080fd5b6130b361286c565b8251815260208301516130c581612b2e565b60208201526130d78460408501612d3f565b6040820152939250505056fea2646970667358221220aa404cddf55107bb4ef2c6beb0224c8f9f889ea8535030db0d36c362777d7cae64736f6c63430008160033", + "devdoc": { + "errors": { + "ERC20InsufficientAllowance(address,uint256,uint256)": [ + { + "details": "Indicates a failure with the `spender`’s `allowance`. Used in transfers.", + "params": { + "allowance": "Amount of tokens a `spender` is allowed to operate with.", + "needed": "Minimum amount required to perform a transfer.", + "spender": "Address that may be allowed to operate on tokens without being their owner." + } + } + ], + "ERC20InsufficientBalance(address,uint256,uint256)": [ + { + "details": "Indicates an error related to the current `balance` of a `sender`. Used in transfers.", + "params": { + "balance": "Current balance for the interacting account.", + "needed": "Minimum amount required to perform a transfer.", + "sender": "Address whose tokens are being transferred." + } + } + ], + "ERC20InvalidApprover(address)": [ + { + "details": "Indicates a failure with the `approver` of a token to be approved. Used in approvals.", + "params": { + "approver": "Address initiating an approval operation." + } + } + ], + "ERC20InvalidReceiver(address)": [ + { + "details": "Indicates a failure with the token `receiver`. Used in transfers.", + "params": { + "receiver": "Address to which tokens are being transferred." + } + } + ], + "ERC20InvalidSender(address)": [ + { + "details": "Indicates a failure with the token `sender`. Used in transfers.", + "params": { + "sender": "Address whose tokens are being transferred." + } + } + ], + "ERC20InvalidSpender(address)": [ + { + "details": "Indicates a failure with the `spender` to be approved. Used in approvals.", + "params": { + "spender": "Address that may be allowed to operate on tokens without being their owner." + } + } + ], + "OwnableInvalidOwner(address)": [ + { + "details": "The owner is not a valid owner account. (eg. `address(0)`)" + } + ], + "OwnableUnauthorizedAccount(address)": [ + { + "details": "The caller account is not authorized to perform an operation." + } + ], + "SafeERC20FailedOperation(address)": [ + { + "details": "An operation with an ERC-20 token failed." + } + ] + }, + "events": { + "Approval(address,address,uint256)": { + "details": "Emitted when the allowance of a `spender` for an `owner` is set by a call to {approve}. `value` is the new allowance." + }, + "PreCrimeSet(address)": { + "details": "Emitted when the preCrime contract address is set.", + "params": { + "preCrimeAddress": "The address of the preCrime contract." + } + }, + "Transfer(address,address,uint256)": { + "details": "Emitted when `value` tokens are moved from one account (`from`) to another (`to`). Note that `value` may be zero." + } + }, + "kind": "dev", + "methods": { + "allowInitializePath((uint32,bytes32,uint64))": { + "details": "This indicates to the endpoint that the OApp has enabled msgs for this particular path to be received.This defaults to assuming if a peer has been set, its initialized. Can be overridden by the OApp if there is other logic to determine this.", + "params": { + "origin": "The origin information containing the source endpoint and sender address." + }, + "returns": { + "_0": "Whether the path has been initialized." + } + }, + "allowance(address,address)": { + "details": "Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` through {transferFrom}. This is zero by default. This value changes when {approve} or {transferFrom} are called." + }, + "approvalRequired()": { + "details": "In the case of OFT where the contract IS the token, approval is NOT required.", + "returns": { + "_0": "requiresApproval Needs approval of the underlying token implementation." + } + }, + "approve(address,uint256)": { + "details": "See {IERC20-approve}. NOTE: If `value` is the maximum `uint256`, the allowance is not updated on `transferFrom`. This is semantically equivalent to an infinite approval. Requirements: - `spender` cannot be the zero address." + }, + "balanceOf(address)": { + "details": "Returns the value of tokens owned by `account`." + }, + "combineOptions(uint32,uint16,bytes)": { + "details": "If there is an enforced lzReceive option: - {gasLimit: 200k, msg.value: 1 ether} AND a caller supplies a lzReceive option: {gasLimit: 100k, msg.value: 0.5 ether} - The resulting options will be {gasLimit: 300k, msg.value: 1.5 ether} when the message is executed on the remote lzReceive() function.This presence of duplicated options is handled off-chain in the verifier/executor.", + "params": { + "_eid": "The endpoint ID.", + "_extraOptions": "Additional options passed by the caller.", + "_msgType": "The OAPP message type." + }, + "returns": { + "_0": "options The combination of caller specified options AND enforced options." + } + }, + "decimals()": { + "details": "Returns the number of decimals used to get its user representation. For example, if `decimals` equals `2`, a balance of `505` tokens should be displayed to a user as `5.05` (`505 / 10 ** 2`). Tokens usually opt for a value of 18, imitating the relationship between Ether and Wei. This is the default value returned by this function, unless it's overridden. NOTE: This information is only used for _display_ purposes: it in no way affects any of the arithmetic of the contract, including {IERC20-balanceOf} and {IERC20-transfer}." + }, + "isComposeMsgSender((uint32,bytes32,uint64),bytes,address)": { + "details": "_origin The origin information containing the source endpoint and sender address. - srcEid: The source chain endpoint ID. - sender: The sender address on the src chain. - nonce: The nonce of the message._message The lzReceive payload.Applications can optionally choose to implement separate composeMsg senders that are NOT the bridging layer.The default sender IS the OAppReceiver implementer.", + "params": { + "_sender": "The sender address." + }, + "returns": { + "_0": "isSender Is a valid sender." + } + }, + "isPeer(uint32,bytes32)": { + "details": "Check if the peer is considered 'trusted' by the OApp.Enables OAppPreCrimeSimulator to check whether a potential Inbound Packet is from a trusted source.", + "params": { + "_eid": "The endpoint ID to check.", + "_peer": "The peer to check." + }, + "returns": { + "_0": "Whether the peer passed is considered 'trusted' by the OApp." + } + }, + "lzReceive((uint32,bytes32,uint64),bytes32,bytes,address,bytes)": { + "details": "Entry point for receiving messages or packets from the endpoint.Entry point for receiving msg/packet from the LayerZero endpoint.", + "params": { + "_executor": "The address of the executor for the received message.", + "_extraData": "Additional arbitrary data provided by the corresponding executor.", + "_guid": "The unique identifier for the received LayerZero message.", + "_message": "The payload of the received message.", + "_origin": "The origin information containing the source endpoint and sender address. - srcEid: The source chain endpoint ID. - sender: The sender address on the src chain. - nonce: The nonce of the message." + } + }, + "lzReceiveAndRevert(((uint32,bytes32,uint64),uint32,address,bytes32,uint256,address,bytes,bytes)[])": { + "details": "Interface for pre-crime simulations. Always reverts at the end with the simulation results.WARNING: MUST revert at the end with the simulation results.Gives the preCrime implementation the ability to mock sending packets to the lzReceive function, WITHOUT actually executing them.", + "params": { + "_packets": "An array of InboundPacket objects representing received packets to be delivered." + } + }, + "lzReceiveSimulate((uint32,bytes32,uint64),bytes32,bytes,address,bytes)": { + "details": "Is effectively an internal function because msg.sender must be address(this). Allows resetting the call stack for 'internal' calls.", + "params": { + "_executor": "The executor address for the packet.", + "_extraData": "Additional data for the packet.", + "_guid": "The unique identifier of the packet.", + "_message": "The message payload of the packet.", + "_origin": "The origin information containing the source endpoint and sender address. - srcEid: The source chain endpoint ID. - sender: The sender address on the src chain. - nonce: The nonce of the message." + } + }, + "mint(address,uint256)": { + "details": "Deliberately unguarded: the demo mints to itself before each send, and a testnet OFT with no way to obtain tokens cannot be used to exercise the DVN at all. Do NOT ship this to a network where the token has value. `virtual` because MyOFTMock declares the same function for the hardhat tests." + }, + "name()": { + "details": "Returns the name of the token." + }, + "nextNonce(uint32,bytes32)": { + "details": "_srcEid The source endpoint ID._sender The sender address.The path nonce starts from 1. If 0 is returned it means that there is NO nonce ordered enforcement.Is required by the off-chain executor to determine the OApp expects msg execution is ordered.This is also enforced by the OApp.By default this is NOT enabled. ie. nextNonce is hardcoded to return 0.", + "returns": { + "nonce": "The next nonce." + } + }, + "oApp()": { + "details": "Retrieves the address of the OApp contract.The simulator contract is the base contract for the OApp by default.If the simulator is a separate contract, override this function.", + "returns": { + "_0": "The address of the OApp contract." + } + }, + "oAppVersion()": { + "returns": { + "receiverVersion": "The version of the OAppReceiver.sol implementation.", + "senderVersion": "The version of the OAppSender.sol implementation." + } + }, + "oftVersion()": { + "details": "interfaceId: This specific interface ID is '0x02e49c2c'.version: Indicates a cross-chain compatible msg encoding with other OFTs.If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented. ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)", + "returns": { + "interfaceId": "The interface ID.", + "version": "The version." + } + }, + "owner()": { + "details": "Returns the address of the current owner." + }, + "quoteOFT((uint32,bytes32,uint256,uint256,bytes,bytes,bytes))": { + "params": { + "_sendParam": "The parameters for the send operation." + }, + "returns": { + "oftFeeDetails": "The details of OFT fees.", + "oftLimit": "The OFT limit information.", + "oftReceipt": "The OFT receipt information." + } + }, + "quoteSend((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),bool)": { + "details": "MessagingFee: LayerZero msg fee - nativeFee: The native fee. - lzTokenFee: The lzToken fee.", + "params": { + "_payInLzToken": "Flag indicating whether the caller is paying in the LZ token.", + "_sendParam": "The parameters for the send() operation." + }, + "returns": { + "msgFee": "The calculated LayerZero messaging fee from the send() operation." + } + }, + "renounceOwnership()": { + "details": "Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner." + }, + "send((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),(uint256,uint256),address)": { + "details": "Executes the send operation.MessagingReceipt: LayerZero msg receipt - guid: The unique identifier for the sent message. - nonce: The nonce of the sent message. - fee: The LayerZero fee incurred for the message.", + "params": { + "_fee": "The calculated fee for the send() operation. - nativeFee: The native fee. - lzTokenFee: The lzToken fee.", + "_refundAddress": "The address to receive any excess funds.", + "_sendParam": "The parameters for the send operation." + }, + "returns": { + "msgReceipt": "The receipt for the send operation.", + "oftReceipt": "The OFT receipt information." + } + }, + "setDelegate(address)": { + "details": "Only the owner/admin of the OApp can call this function.Provides the ability for a delegate to set configs, on behalf of the OApp, directly on the Endpoint contract.", + "params": { + "_delegate": "The address of the delegate to be set." + } + }, + "setEnforcedOptions((uint32,uint16,bytes)[])": { + "details": "Sets the enforced options for specific endpoint and message type combinations.Only the owner/admin of the OApp can call this function.Provides a way for the OApp to enforce things like paying for PreCrime, AND/OR minimum dst lzReceive gas amounts etc.These enforced options can vary as the potential options/execution on the remote may differ as per the msgType. eg. Amount of lzReceive() gas necessary to deliver a lzCompose() message adds overhead you dont want to pay if you are only making a standard LayerZero message ie. lzReceive() WITHOUT sendCompose().", + "params": { + "_enforcedOptions": "An array of EnforcedOptionParam structures specifying enforced options." + } + }, + "setMsgInspector(address)": { + "details": "Sets the message inspector address for the OFT.This is an optional contract that can be used to inspect both 'message' and 'options'.Set it to address(0) to disable it, or set it to a contract address to enable it.", + "params": { + "_msgInspector": "The address of the message inspector." + } + }, + "setPeer(uint32,bytes32)": { + "details": "Only the owner/admin of the OApp can call this function.Indicates that the peer is trusted to send LayerZero messages to this OApp.Set this to bytes32(0) to remove the peer address.Peer is a bytes32 to accommodate non-evm chains.", + "params": { + "_eid": "The endpoint ID.", + "_peer": "The address of the peer to be associated with the corresponding endpoint." + } + }, + "setPreCrime(address)": { + "details": "Sets the preCrime contract address.", + "params": { + "_preCrime": "The address of the preCrime contract." + } + }, + "sharedDecimals()": { + "details": "Retrieves the shared decimals of the OFT.Sets an implicit cap on the amount of tokens, over uint64.max() will need some sort of outbound cap / totalSupply cap Lowest common decimal denominator between chains. Defaults to 6 decimal places to provide up to 18,446,744,073,709.551615 units (max uint64). For tokens exceeding this totalSupply(), they will need to override the sharedDecimals function with something smaller. ie. 4 sharedDecimals would be 1,844,674,407,370,955.1615", + "returns": { + "_0": "The shared decimals of the OFT." + } + }, + "symbol()": { + "details": "Returns the symbol of the token, usually a shorter version of the name." + }, + "token()": { + "details": "Retrieves the address of the underlying ERC20 implementation.In the case of OFT, address(this) and erc20 are the same contract.", + "returns": { + "_0": "The address of the OFT token." + } + }, + "totalSupply()": { + "details": "Returns the value of tokens in existence." + }, + "transfer(address,uint256)": { + "details": "See {IERC20-transfer}. Requirements: - `to` cannot be the zero address. - the caller must have a balance of at least `value`." + }, + "transferFrom(address,address,uint256)": { + "details": "See {IERC20-transferFrom}. Skips emitting an {Approval} event indicating an allowance update. This is not required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve]. NOTE: Does not update the allowance if the current allowance is the maximum `uint256`. Requirements: - `from` and `to` cannot be the zero address. - `from` must have a balance of at least `value`. - the caller must have allowance for ``from``'s tokens of at least `value`." + }, + "transferOwnership(address)": { + "details": "Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner." + } + }, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "allowInitializePath((uint32,bytes32,uint64))": { + "notice": "Checks if the path initialization is allowed based on the provided origin." + }, + "approvalRequired()": { + "notice": "Indicates whether the OFT contract requires approval of the 'token()' to send." + }, + "combineOptions(uint32,uint16,bytes)": { + "notice": "Combines options for a given endpoint and message type." + }, + "endpoint()": { + "notice": "Retrieves the LayerZero endpoint associated with the OApp." + }, + "isComposeMsgSender((uint32,bytes32,uint64),bytes,address)": { + "notice": "Indicates whether an address is an approved composeMsg sender to the Endpoint." + }, + "mint(address,uint256)": { + "notice": "Open mint, TESTNET ONLY — same as ToyOFT, so the demo tasks work against either." + }, + "nextNonce(uint32,bytes32)": { + "notice": "Retrieves the next nonce for a given source endpoint and sender address." + }, + "oAppVersion()": { + "notice": "Retrieves the OApp version information." + }, + "oftVersion()": { + "notice": "Retrieves interfaceID and the version of the OFT." + }, + "peers(uint32)": { + "notice": "Retrieves the peer (OApp) associated with a corresponding endpoint." + }, + "quoteOFT((uint32,bytes32,uint256,uint256,bytes,bytes,bytes))": { + "notice": "Provides the fee breakdown and settings data for an OFT. Unused in the default implementation." + }, + "quoteSend((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),bool)": { + "notice": "Provides a quote for the send() operation." + }, + "setDelegate(address)": { + "notice": "Sets the delegate address for the OApp." + }, + "setPeer(uint32,bytes32)": { + "notice": "Sets the peer address (OApp instance) for a corresponding endpoint." + } + }, + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 3861, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "t_address" + }, + { + "astId": 1390, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "peers", + "offset": 0, + "slot": "1", + "type": "t_mapping(t_uint32,t_bytes32)" + }, + { + "astId": 2166, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "preCrime", + "offset": 0, + "slot": "2", + "type": "t_address" + }, + { + "astId": 2006, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "enforcedOptions", + "offset": 0, + "slot": "3", + "type": "t_mapping(t_uint32,t_mapping(t_uint16,t_bytes_storage))" + }, + { + "astId": 2786, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "msgInspector", + "offset": 0, + "slot": "4", + "type": "t_address" + }, + { + "astId": 4250, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "_balances", + "offset": 0, + "slot": "5", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 4256, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "_allowances", + "offset": 0, + "slot": "6", + "type": "t_mapping(t_address,t_mapping(t_address,t_uint256))" + }, + { + "astId": 4258, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "_totalSupply", + "offset": 0, + "slot": "7", + "type": "t_uint256" + }, + { + "astId": 4260, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "_name", + "offset": 0, + "slot": "8", + "type": "t_string_storage" + }, + { + "astId": 4262, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "_symbol", + "offset": 0, + "slot": "9", + "type": "t_string_storage" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_bytes32": { + "encoding": "inplace", + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_bytes_storage": { + "encoding": "bytes", + "label": "bytes", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32", + "value": "t_mapping(t_address,t_uint256)" + }, + "t_mapping(t_address,t_uint256)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_mapping(t_uint16,t_bytes_storage)": { + "encoding": "mapping", + "key": "t_uint16", + "label": "mapping(uint16 => bytes)", + "numberOfBytes": "32", + "value": "t_bytes_storage" + }, + "t_mapping(t_uint32,t_bytes32)": { + "encoding": "mapping", + "key": "t_uint32", + "label": "mapping(uint32 => bytes32)", + "numberOfBytes": "32", + "value": "t_bytes32" + }, + "t_mapping(t_uint32,t_mapping(t_uint16,t_bytes_storage))": { + "encoding": "mapping", + "key": "t_uint32", + "label": "mapping(uint32 => mapping(uint16 => bytes))", + "numberOfBytes": "32", + "value": "t_mapping(t_uint16,t_bytes_storage)" + }, + "t_string_storage": { + "encoding": "bytes", + "label": "string", + "numberOfBytes": "32" + }, + "t_uint16": { + "encoding": "inplace", + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint32": { + "encoding": "inplace", + "label": "uint32", + "numberOfBytes": "4" + } + } + } +} \ No newline at end of file diff --git a/deployments/base-sepolia/RiskyProxyMock.json b/deployments/base-sepolia/RiskyProxyMock.json new file mode 100644 index 0000000..13bd08b --- /dev/null +++ b/deployments/base-sepolia/RiskyProxyMock.json @@ -0,0 +1,111 @@ +{ + "address": "0xeBb646C8eD3A06d37bd779C994A9d479abC788d3", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_admin", + "type": "address" + }, + { + "internalType": "address", + "name": "_implementation", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address", + "name": "a", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "i", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "transactionHash": "0x9920c73b138937aeb753560720118dba07235f7793c65dfcf8d79a7fb991c33e", + "receipt": { + "to": null, + "from": "0x8583894d0e57e42abb83039537f314490038efa0", + "contractAddress": "0xeBb646C8eD3A06d37bd779C994A9d479abC788d3", + "transactionIndex": 8, + "gasUsed": "149244", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x9920c73b138937aeb753560720118dba07235f7793c65dfcf8d79a7fb991c33e", + "logs": [], + "blockNumber": 44883456, + "cumulativeGasUsed": "2288971", + "status": 1, + "byzantium": true + }, + "args": [ + "0x000000000000000000000000000000000000dEaD", + "0x8583894d0e57e42abb83039537f314490038efa0" + ], + "numDeployments": 1, + "solcInputHash": "91f67dad93f3438967ecce8dd1aaa287", + "metadata": "{\"compiler\":{\"version\":\"0.8.22+commit.4fc1097e\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_admin\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_implementation\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"admin\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"a\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"implementation\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"i\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"details\":\"The engine reads the two EIP-1967 slots directly (see `assess/providers/contract.ts`): an implementation slot that is set means the code behind this address can change, and the admin slot names whoever can change it. It then looks that admin up in the risk store \\u2014 a flagged admin is the signal, because today's clean code says nothing about tomorrow's if a sanctioned party can swap it out. The slots are written straight to storage rather than by deploying a real proxy: what is being demonstrated is the engine's reading of them, and a forwarding proxy would add a delegatecall path with nothing to delegate to.\",\"kind\":\"dev\",\"methods\":{\"constructor\":{\"params\":{\"_admin\":\"The address to present as able to upgrade this contract. Point it at an address the operator has flagged (e.g. one in TEST_DENYLIST) for the check to fire.\",\"_implementation\":\"Any non-zero address; its only job is to make the proxy slot set.\"}}},\"stateVariables\":{\"SLOT_ADMIN\":{\"details\":\"keccak256(\\\"eip1967.proxy.admin\\\") - 1\"},\"SLOT_IMPLEMENTATION\":{\"details\":\"keccak256(\\\"eip1967.proxy.implementation\\\") - 1\"}},\"title\":\"RiskyProxyMock\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"admin()\":{\"notice\":\"The admin as stored in the EIP-1967 slot, for anyone reading it the easy way.\"},\"implementation()\":{\"notice\":\"The implementation as stored in the EIP-1967 slot.\"}},\"notice\":\"A testnet decoy that looks like an upgradeable proxy controlled by a flagged address, for exercising the risk engine's `contract_admin_risk` check.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/mocks/RiskyProxyMock.sol\":\"RiskyProxyMock\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"contracts/mocks/RiskyProxyMock.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.22;\\n\\n/// @title RiskyProxyMock\\n/// @notice A testnet decoy that looks like an upgradeable proxy controlled by a flagged address,\\n/// for exercising the risk engine's `contract_admin_risk` check.\\n/// @dev The engine reads the two EIP-1967 slots directly (see `assess/providers/contract.ts`):\\n/// an implementation slot that is set means the code behind this address can change, and the\\n/// admin slot names whoever can change it. It then looks that admin up in the risk store \\u2014\\n/// a flagged admin is the signal, because today's clean code says nothing about tomorrow's\\n/// if a sanctioned party can swap it out.\\n///\\n/// The slots are written straight to storage rather than by deploying a real proxy: what is\\n/// being demonstrated is the engine's reading of them, and a forwarding proxy would add a\\n/// delegatecall path with nothing to delegate to.\\ncontract RiskyProxyMock {\\n /// @dev keccak256(\\\"eip1967.proxy.implementation\\\") - 1\\n bytes32 private constant SLOT_IMPLEMENTATION =\\n 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\\n /// @dev keccak256(\\\"eip1967.proxy.admin\\\") - 1\\n bytes32 private constant SLOT_ADMIN = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;\\n\\n /// @param _admin The address to present as able to upgrade this contract. Point it at an\\n /// address the operator has flagged (e.g. one in TEST_DENYLIST) for the check to fire.\\n /// @param _implementation Any non-zero address; its only job is to make the proxy slot set.\\n constructor(address _admin, address _implementation) {\\n require(_admin != address(0), \\\"zero admin\\\");\\n require(_implementation != address(0), \\\"zero implementation\\\");\\n assembly {\\n sstore(SLOT_ADMIN, _admin)\\n sstore(SLOT_IMPLEMENTATION, _implementation)\\n }\\n }\\n\\n /// @notice The admin as stored in the EIP-1967 slot, for anyone reading it the easy way.\\n function admin() external view returns (address a) {\\n assembly {\\n a := sload(SLOT_ADMIN)\\n }\\n }\\n\\n /// @notice The implementation as stored in the EIP-1967 slot.\\n function implementation() external view returns (address i) {\\n assembly {\\n i := sload(SLOT_IMPLEMENTATION)\\n }\\n }\\n}\\n\",\"keccak256\":\"0xf54c10753cd83e2a3f3b58cac49ba5a37eb0314ff51853ce443fc5917cc42f03\",\"license\":\"MIT\"}},\"version\":1}", + "bytecode": "0x608060405234801561001057600080fd5b5060405161024838038061024883398101604081905261002f91610135565b6001600160a01b0382166100775760405162461bcd60e51b815260206004820152600a6024820152693d32b9379030b236b4b760b11b60448201526064015b60405180910390fd5b6001600160a01b0381166100cd5760405162461bcd60e51b815260206004820152601360248201527f7a65726f20696d706c656d656e746174696f6e00000000000000000000000000604482015260640161006e565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103919091557f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc55610168565b80516001600160a01b038116811461013057600080fd5b919050565b6000806040838503121561014857600080fd5b61015183610119565b915061015f60208401610119565b90509250929050565b60d2806101766000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c80635c60da1b146037578063f851a440146076575b600080fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545b6040516001600160a01b03909116815260200160405180910390f35b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610354605a56fea2646970667358221220db4d640c5bd6b171ede814a8f22af946c7a8d98b7ba3fb5d90d436509788860864736f6c63430008160033", + "deployedBytecode": "0x6080604052348015600f57600080fd5b506004361060325760003560e01c80635c60da1b146037578063f851a440146076575b600080fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545b6040516001600160a01b03909116815260200160405180910390f35b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610354605a56fea2646970667358221220db4d640c5bd6b171ede814a8f22af946c7a8d98b7ba3fb5d90d436509788860864736f6c63430008160033", + "devdoc": { + "details": "The engine reads the two EIP-1967 slots directly (see `assess/providers/contract.ts`): an implementation slot that is set means the code behind this address can change, and the admin slot names whoever can change it. It then looks that admin up in the risk store — a flagged admin is the signal, because today's clean code says nothing about tomorrow's if a sanctioned party can swap it out. The slots are written straight to storage rather than by deploying a real proxy: what is being demonstrated is the engine's reading of them, and a forwarding proxy would add a delegatecall path with nothing to delegate to.", + "kind": "dev", + "methods": { + "constructor": { + "params": { + "_admin": "The address to present as able to upgrade this contract. Point it at an address the operator has flagged (e.g. one in TEST_DENYLIST) for the check to fire.", + "_implementation": "Any non-zero address; its only job is to make the proxy slot set." + } + } + }, + "stateVariables": { + "SLOT_ADMIN": { + "details": "keccak256(\"eip1967.proxy.admin\") - 1" + }, + "SLOT_IMPLEMENTATION": { + "details": "keccak256(\"eip1967.proxy.implementation\") - 1" + } + }, + "title": "RiskyProxyMock", + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "admin()": { + "notice": "The admin as stored in the EIP-1967 slot, for anyone reading it the easy way." + }, + "implementation()": { + "notice": "The implementation as stored in the EIP-1967 slot." + } + }, + "notice": "A testnet decoy that looks like an upgradeable proxy controlled by a flagged address, for exercising the risk engine's `contract_admin_risk` check.", + "version": 1 + }, + "storageLayout": { + "storage": [], + "types": null + } +} \ No newline at end of file diff --git a/deployments/base-sepolia/solcInputs/31caad50e704c80e4a3252d0a262b59d.json b/deployments/base-sepolia/solcInputs/31caad50e704c80e4a3252d0a262b59d.json new file mode 100644 index 0000000..7368160 --- /dev/null +++ b/deployments/base-sepolia/solcInputs/31caad50e704c80e4a3252d0a262b59d.json @@ -0,0 +1,51 @@ +{ + "language": "Solidity", + "sources": { + "@openzeppelin/contracts/interfaces/draft-IERC6093.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.5.0) (interfaces/draft-IERC6093.sol)\n\npragma solidity >=0.8.4;\n\n/**\n * @dev Standard ERC-20 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-20 tokens.\n */\ninterface IERC20Errors {\n /**\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n * @param balance Current balance for the interacting account.\n * @param needed Minimum amount required to perform a transfer.\n */\n error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);\n\n /**\n * @dev Indicates a failure with the token `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n */\n error ERC20InvalidSender(address sender);\n\n /**\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\n * @param receiver Address to which tokens are being transferred.\n */\n error ERC20InvalidReceiver(address receiver);\n\n /**\n * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.\n * @param spender Address that may be allowed to operate on tokens without being their owner.\n * @param allowance Amount of tokens a `spender` is allowed to operate with.\n * @param needed Minimum amount required to perform a transfer.\n */\n error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);\n\n /**\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n * @param approver Address initiating an approval operation.\n */\n error ERC20InvalidApprover(address approver);\n\n /**\n * @dev Indicates a failure with the `spender` to be approved. Used in approvals.\n * @param spender Address that may be allowed to operate on tokens without being their owner.\n */\n error ERC20InvalidSpender(address spender);\n}\n\n/**\n * @dev Standard ERC-721 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-721 tokens.\n */\ninterface IERC721Errors {\n /**\n * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-721.\n * Used in balance queries.\n * @param owner Address of the current owner of a token.\n */\n error ERC721InvalidOwner(address owner);\n\n /**\n * @dev Indicates a `tokenId` whose `owner` is the zero address.\n * @param tokenId Identifier number of a token.\n */\n error ERC721NonexistentToken(uint256 tokenId);\n\n /**\n * @dev Indicates an error related to the ownership over a particular token. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n * @param tokenId Identifier number of a token.\n * @param owner Address of the current owner of a token.\n */\n error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);\n\n /**\n * @dev Indicates a failure with the token `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n */\n error ERC721InvalidSender(address sender);\n\n /**\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\n * @param receiver Address to which tokens are being transferred.\n */\n error ERC721InvalidReceiver(address receiver);\n\n /**\n * @dev Indicates a failure with the `operator`’s approval. Used in transfers.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n * @param tokenId Identifier number of a token.\n */\n error ERC721InsufficientApproval(address operator, uint256 tokenId);\n\n /**\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n * @param approver Address initiating an approval operation.\n */\n error ERC721InvalidApprover(address approver);\n\n /**\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n */\n error ERC721InvalidOperator(address operator);\n}\n\n/**\n * @dev Standard ERC-1155 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-1155 tokens.\n */\ninterface IERC1155Errors {\n /**\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n * @param balance Current balance for the interacting account.\n * @param needed Minimum amount required to perform a transfer.\n * @param tokenId Identifier number of a token.\n */\n error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);\n\n /**\n * @dev Indicates a failure with the token `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n */\n error ERC1155InvalidSender(address sender);\n\n /**\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\n * @param receiver Address to which tokens are being transferred.\n */\n error ERC1155InvalidReceiver(address receiver);\n\n /**\n * @dev Indicates a failure with the `operator`’s approval. Used in transfers.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n * @param owner Address of the current owner of a token.\n */\n error ERC1155MissingApprovalForAll(address operator, address owner);\n\n /**\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n * @param approver Address initiating an approval operation.\n */\n error ERC1155InvalidApprover(address approver);\n\n /**\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n */\n error ERC1155InvalidOperator(address operator);\n\n /**\n * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.\n * Used in batch transfers.\n * @param idsLength Length of the array of token identifiers\n * @param valuesLength Length of the array of token amounts\n */\n error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/ERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.5.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"./IERC20.sol\";\nimport {IERC20Metadata} from \"./extensions/IERC20Metadata.sol\";\nimport {Context} from \"../../utils/Context.sol\";\nimport {IERC20Errors} from \"../../interfaces/draft-IERC6093.sol\";\n\n/**\n * @dev Implementation of the {IERC20} interface.\n *\n * This implementation is agnostic to the way tokens are created. This means\n * that a supply mechanism has to be added in a derived contract using {_mint}.\n *\n * TIP: For a detailed writeup see our guide\n * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How\n * to implement supply mechanisms].\n *\n * The default value of {decimals} is 18. To change this, you should override\n * this function so it returns a different value.\n *\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\n * instead returning `false` on failure. This behavior is nonetheless\n * conventional and does not conflict with the expectations of ERC-20\n * applications.\n */\nabstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {\n mapping(address account => uint256) private _balances;\n\n mapping(address account => mapping(address spender => uint256)) private _allowances;\n\n uint256 private _totalSupply;\n\n string private _name;\n string private _symbol;\n\n /**\n * @dev Sets the values for {name} and {symbol}.\n *\n * Both values are immutable: they can only be set once during construction.\n */\n constructor(string memory name_, string memory symbol_) {\n _name = name_;\n _symbol = symbol_;\n }\n\n /**\n * @dev Returns the name of the token.\n */\n function name() public view virtual returns (string memory) {\n return _name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view virtual returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei. This is the default value returned by this function, unless\n * it's overridden.\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view virtual returns (uint8) {\n return 18;\n }\n\n /// @inheritdoc IERC20\n function totalSupply() public view virtual returns (uint256) {\n return _totalSupply;\n }\n\n /// @inheritdoc IERC20\n function balanceOf(address account) public view virtual returns (uint256) {\n return _balances[account];\n }\n\n /**\n * @dev See {IERC20-transfer}.\n *\n * Requirements:\n *\n * - `to` cannot be the zero address.\n * - the caller must have a balance of at least `value`.\n */\n function transfer(address to, uint256 value) public virtual returns (bool) {\n address owner = _msgSender();\n _transfer(owner, to, value);\n return true;\n }\n\n /// @inheritdoc IERC20\n function allowance(address owner, address spender) public view virtual returns (uint256) {\n return _allowances[owner][spender];\n }\n\n /**\n * @dev See {IERC20-approve}.\n *\n * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on\n * `transferFrom`. This is semantically equivalent to an infinite approval.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function approve(address spender, uint256 value) public virtual returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, value);\n return true;\n }\n\n /**\n * @dev See {IERC20-transferFrom}.\n *\n * Skips emitting an {Approval} event indicating an allowance update. This is not\n * required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].\n *\n * NOTE: Does not update the allowance if the current allowance\n * is the maximum `uint256`.\n *\n * Requirements:\n *\n * - `from` and `to` cannot be the zero address.\n * - `from` must have a balance of at least `value`.\n * - the caller must have allowance for ``from``'s tokens of at least\n * `value`.\n */\n function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {\n address spender = _msgSender();\n _spendAllowance(from, spender, value);\n _transfer(from, to, value);\n return true;\n }\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to`.\n *\n * This internal function is equivalent to {transfer}, and can be used to\n * e.g. implement automatic token fees, slashing mechanisms, etc.\n *\n * Emits a {Transfer} event.\n *\n * NOTE: This function is not virtual, {_update} should be overridden instead.\n */\n function _transfer(address from, address to, uint256 value) internal {\n if (from == address(0)) {\n revert ERC20InvalidSender(address(0));\n }\n if (to == address(0)) {\n revert ERC20InvalidReceiver(address(0));\n }\n _update(from, to, value);\n }\n\n /**\n * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`\n * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding\n * this function.\n *\n * Emits a {Transfer} event.\n */\n function _update(address from, address to, uint256 value) internal virtual {\n if (from == address(0)) {\n // Overflow check required: The rest of the code assumes that totalSupply never overflows\n _totalSupply += value;\n } else {\n uint256 fromBalance = _balances[from];\n if (fromBalance < value) {\n revert ERC20InsufficientBalance(from, fromBalance, value);\n }\n unchecked {\n // Overflow not possible: value <= fromBalance <= totalSupply.\n _balances[from] = fromBalance - value;\n }\n }\n\n if (to == address(0)) {\n unchecked {\n // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.\n _totalSupply -= value;\n }\n } else {\n unchecked {\n // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.\n _balances[to] += value;\n }\n }\n\n emit Transfer(from, to, value);\n }\n\n /**\n * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).\n * Relies on the `_update` mechanism\n *\n * Emits a {Transfer} event with `from` set to the zero address.\n *\n * NOTE: This function is not virtual, {_update} should be overridden instead.\n */\n function _mint(address account, uint256 value) internal {\n if (account == address(0)) {\n revert ERC20InvalidReceiver(address(0));\n }\n _update(address(0), account, value);\n }\n\n /**\n * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.\n * Relies on the `_update` mechanism.\n *\n * Emits a {Transfer} event with `to` set to the zero address.\n *\n * NOTE: This function is not virtual, {_update} should be overridden instead\n */\n function _burn(address account, uint256 value) internal {\n if (account == address(0)) {\n revert ERC20InvalidSender(address(0));\n }\n _update(account, address(0), value);\n }\n\n /**\n * @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.\n *\n * This internal function is equivalent to `approve`, and can be used to\n * e.g. set automatic allowances for certain subsystems, etc.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `owner` cannot be the zero address.\n * - `spender` cannot be the zero address.\n *\n * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.\n */\n function _approve(address owner, address spender, uint256 value) internal {\n _approve(owner, spender, value, true);\n }\n\n /**\n * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.\n *\n * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by\n * `_spendAllowance` during the `transferFrom` operation sets the flag to false. This saves gas by not emitting any\n * `Approval` event during `transferFrom` operations.\n *\n * Anyone who wishes to continue emitting `Approval` events on the `transferFrom` operation can force the flag to\n * true using the following override:\n *\n * ```solidity\n * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {\n * super._approve(owner, spender, value, true);\n * }\n * ```\n *\n * Requirements are the same as {_approve}.\n */\n function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {\n if (owner == address(0)) {\n revert ERC20InvalidApprover(address(0));\n }\n if (spender == address(0)) {\n revert ERC20InvalidSpender(address(0));\n }\n _allowances[owner][spender] = value;\n if (emitEvent) {\n emit Approval(owner, spender, value);\n }\n }\n\n /**\n * @dev Updates `owner`'s allowance for `spender` based on spent `value`.\n *\n * Does not update the allowance value in case of infinite allowance.\n * Revert if not enough allowance is available.\n *\n * Does not emit an {Approval} event.\n */\n function _spendAllowance(address owner, address spender, uint256 value) internal virtual {\n uint256 currentAllowance = allowance(owner, spender);\n if (currentAllowance < type(uint256).max) {\n if (currentAllowance < value) {\n revert ERC20InsufficientAllowance(spender, currentAllowance, value);\n }\n unchecked {\n _approve(owner, spender, currentAllowance - value, false);\n }\n }\n }\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/extensions/IERC20Metadata.sol)\n\npragma solidity >=0.6.2;\n\nimport {IERC20} from \"../IERC20.sol\";\n\n/**\n * @dev Interface for the optional metadata functions from the ERC-20 standard.\n */\ninterface IERC20Metadata is IERC20 {\n /**\n * @dev Returns the name of the token.\n */\n function name() external view returns (string memory);\n\n /**\n * @dev Returns the symbol of the token.\n */\n function symbol() external view returns (string memory);\n\n /**\n * @dev Returns the decimals places of the token.\n */\n function decimals() external view returns (uint8);\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)\n\npragma solidity >=0.4.16;\n\n/**\n * @dev Interface of the ERC-20 standard as defined in the ERC.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the value of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the value of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves a `value` amount of tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 value) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n * caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 value) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to` using the\n * allowance mechanism. `value` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address from, address to, uint256 value) external returns (bool);\n}\n" + }, + "@openzeppelin/contracts/utils/Context.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n function _contextSuffixLength() internal view virtual returns (uint256) {\n return 0;\n }\n}\n" + }, + "contracts/mocks/FakeStablecoinMock.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.22;\n\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\n/// @title FakeStablecoinMock\n/// @notice A testnet decoy that claims to be USDC, for exercising the risk engine's\n/// impersonation check. It is NOT a stablecoin and holds no value.\n/// @dev The engine's token screening resolves a subject's underlying token through `token()`,\n/// reads `symbol()`/`decimals()`, and compares the address against the chain's canonical\n/// issuer (see `CANONICAL_STABLECOINS` in worker/assess/providers/token.ts). Claiming a\n/// watched symbol from a non-canonical address is exactly the pattern\n/// `fake_stablecoin_suspect` exists to catch — so this contract asserts the symbol and\n/// nothing else. Deploy only to testnets.\ncontract FakeStablecoinMock is ERC20 {\n constructor() ERC20(\"USD Coin\", \"USDC\") {}\n\n /// @dev Six, like the real thing: the check is about the address, and matching the decimals\n /// keeps the decoy from being dismissed on a detail the engine does not rely on.\n function decimals() public pure override returns (uint8) {\n return 6;\n }\n\n /// @notice Reports itself as its own underlying token.\n /// @dev This is what makes the engine treat the address as a token rather than a plain OApp:\n /// `resolveToken` calls `token()` and screens whatever address comes back.\n function token() external view returns (address) {\n return address(this);\n }\n\n /// @notice Open mint, testnet only — a decoy with no supply is harder to look at in an explorer.\n function mint(address _to, uint256 _amount) external {\n _mint(_to, _amount);\n }\n}\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "evmVersion": "paris", + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + "devdoc", + "userdoc", + "storageLayout", + "evm.gasEstimates" + ], + "": [ + "ast" + ] + } + }, + "metadata": { + "useLiteralContent": true + } + } +} \ No newline at end of file diff --git a/deployments/base-sepolia/solcInputs/557c64b7ef69b127f89738ff42782948.json b/deployments/base-sepolia/solcInputs/557c64b7ef69b127f89738ff42782948.json new file mode 100644 index 0000000..3aa2108 --- /dev/null +++ b/deployments/base-sepolia/solcInputs/557c64b7ef69b127f89738ff42782948.json @@ -0,0 +1,51 @@ +{ + "language": "Solidity", + "sources": { + "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/ILayerZeroDVN.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\ninterface ILayerZeroDVN {\n struct AssignJobParam {\n uint32 dstEid;\n bytes packetHeader;\n bytes32 payloadHash;\n uint64 confirmations;\n address sender;\n }\n\n // @notice query price and assign jobs at the same time\n // @param _dstEid - the destination endpoint identifier\n // @param _packetHeader - version + nonce + path\n // @param _payloadHash - hash of guid + message\n // @param _confirmations - block confirmation delay before relaying blocks\n // @param _sender - the source sending contract address\n // @param _options - options\n function assignJob(AssignJobParam calldata _param, bytes calldata _options) external payable returns (uint256 fee);\n\n // @notice query the dvn fee for relaying block information to the destination chain\n // @param _dstEid the destination endpoint identifier\n // @param _confirmations - block confirmation delay before relaying blocks\n // @param _sender - the source sending contract address\n // @param _options - options\n function getFee(\n uint32 _dstEid,\n uint64 _confirmations,\n address _sender,\n bytes calldata _options\n ) external view returns (uint256 fee);\n}\n" + }, + "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/IReceiveUlnE2.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\n/// @dev should be implemented by the ReceiveUln302 contract and future ReceiveUln contracts on EndpointV2\ninterface IReceiveUlnE2 {\n /// @notice for each dvn to verify the payload\n /// @dev this function signature 0x0223536e\n function verify(bytes calldata _packetHeader, bytes32 _payloadHash, uint64 _confirmations) external;\n\n /// @notice verify the payload at endpoint, will check if all DVNs verified\n function commitVerification(bytes calldata _packetHeader, bytes32 _payloadHash) external;\n}\n" + }, + "@openzeppelin/contracts/access/Ownable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\n\npragma solidity ^0.8.20;\n\nimport {Context} from \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * The initial owner is set to the address provided by the deployer. This can\n * later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n /**\n * @dev The caller account is not authorized to perform an operation.\n */\n error OwnableUnauthorizedAccount(address account);\n\n /**\n * @dev The owner is not a valid owner account. (eg. `address(0)`)\n */\n error OwnableInvalidOwner(address owner);\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\n */\n constructor(address initialOwner) {\n if (initialOwner == address(0)) {\n revert OwnableInvalidOwner(address(0));\n }\n _transferOwnership(initialOwner);\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n if (owner() != _msgSender()) {\n revert OwnableUnauthorizedAccount(_msgSender());\n }\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby disabling any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n if (newOwner == address(0)) {\n revert OwnableInvalidOwner(address(0));\n }\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n" + }, + "@openzeppelin/contracts/utils/Context.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n function _contextSuffixLength() internal view virtual returns (uint256) {\n return 0;\n }\n}\n" + }, + "contracts/ComplianceDVN.sol": { + "content": "// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.22;\r\n\r\nimport { Ownable } from \"@openzeppelin/contracts/access/Ownable.sol\";\r\nimport { ILayerZeroDVN } from \"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/ILayerZeroDVN.sol\";\r\nimport { IReceiveUlnE2 } from \"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/IReceiveUlnE2.sol\";\r\n\r\n/// @title ComplianceDVN\r\n/// @notice Thin LayerZero V2 DVN. All compliance judgment is off-chain; the on-chain\r\n/// contract only conforms to the worker-job interface and gates the destination\r\n/// attestation behind an operator key. Withholding `submitVerification` IS the veto.\r\ncontract ComplianceDVN is ILayerZeroDVN, Ownable {\r\n address public operator; // off-chain worker key\r\n address public receiveUln; // ReceiveUln302 on this chain\r\n uint256 public fee;\r\n\r\n event JobAssigned(uint32 dstEid, bytes32 payloadHash, uint64 confirmations, address sender);\r\n event OperatorSet(address operator);\r\n event ReceiveUlnSet(address receiveUln);\r\n event FeeSet(uint256 fee);\r\n\r\n /// @notice A held packet cleared for verification by the owner. Deliberately owner-only:\r\n /// the worker holds only the operator key, so it cannot approve its own holds.\r\n event PacketApproved(bytes32 indexed payloadHash, address approver);\r\n\r\n /// @notice The risk decision behind a packet's outcome.\r\n /// @param payloadHash the packet this verdict is about\r\n /// @param action ACTION_* below\r\n /// @param score 0-100 risk score the action was derived from\r\n /// @param reasonMask bitmask of reason codes; bit assignments are append-only and\r\n /// documented in the worker's `assess/verdict.ts`\r\n /// @param evidenceHash keccak256 of the canonical evidence document held off-chain\r\n event RiskVerdict(\r\n bytes32 indexed payloadHash,\r\n uint8 action,\r\n uint16 score,\r\n uint256 reasonMask,\r\n bytes32 evidenceHash\r\n );\r\n\r\n /// @dev Action codes. These are part of the event ABI: an indexer decoding old logs relies\r\n /// on them, so the numbering is permanent. Kept in sync with the worker's ACTION_CODES.\r\n uint8 public constant ACTION_ALLOW = 0;\r\n uint8 public constant ACTION_DELAY = 1;\r\n uint8 public constant ACTION_MANUAL_REVIEW = 2;\r\n uint8 public constant ACTION_BLOCK = 3;\r\n\r\n error NotOperator();\r\n error UnknownAction(uint8 action);\r\n /// @dev Submitting a verification asserts the packet was allowed; any other action would be\r\n /// a self-contradicting record.\r\n error VerificationRequiresAllow(uint8 action);\r\n /// @dev An allow rides along on `submitVerification`, so recording one separately would\r\n /// double-report the same outcome.\r\n error AllowNotSeparatelyRecorded();\r\n\r\n modifier onlyOperator() {\r\n if (msg.sender != operator) revert NotOperator();\r\n _;\r\n }\r\n\r\n constructor(address _owner, address _operator, address _receiveUln, uint256 _fee) Ownable(_owner) {\r\n require(_operator != address(0), \"zero operator\");\r\n require(_receiveUln != address(0), \"zero receiveUln\");\r\n operator = _operator;\r\n receiveUln = _receiveUln;\r\n fee = _fee;\r\n }\r\n\r\n function getFee(\r\n uint32 /*_dstEid*/,\r\n uint64 /*_confirmations*/,\r\n address /*_sender*/,\r\n bytes calldata /*_options*/\r\n ) external view returns (uint256) {\r\n return fee;\r\n }\r\n\r\n function assignJob(AssignJobParam calldata _param, bytes calldata) external payable returns (uint256) {\r\n // NOTE: SendUln302 calls assignJob WITHOUT forwarding value (msg.value == 0); the\r\n // messagelib accrues each worker's fee internally and workers withdraw separately\r\n // (see SendUlnBase._assignJobs). So we must NOT require msg.value >= fee here — doing\r\n // so reverts every real send. We simply record the job and return our fee quote.\r\n emit JobAssigned(_param.dstEid, _param.payloadHash, _param.confirmations, _param.sender);\r\n return fee;\r\n }\r\n\r\n /// @notice Attest a packet and record the risk verdict that permitted it, in one call.\r\n /// @dev The verdict rides along at no extra transaction cost, so an allowed packet always\r\n /// carries an auditable reason for having been allowed. `action` must be ACTION_ALLOW:\r\n /// a packet that was blocked or held cannot also have been verified. An owner-approved\r\n /// release is reported as ACTION_ALLOW too — a human allowed it — with the reason mask\r\n /// still carrying why it had been held.\r\n function submitVerification(\r\n bytes calldata packetHeader,\r\n bytes32 payloadHash,\r\n uint64 confirmations,\r\n uint8 action,\r\n uint16 score,\r\n uint256 reasonMask,\r\n bytes32 evidenceHash\r\n ) external onlyOperator {\r\n if (action != ACTION_ALLOW) revert VerificationRequiresAllow(action);\r\n IReceiveUlnE2(receiveUln).verify(packetHeader, payloadHash, confirmations);\r\n emit RiskVerdict(payloadHash, action, score, reasonMask, evidenceHash);\r\n }\r\n\r\n /// @notice Record a verdict for a packet that was NOT verified.\r\n /// @dev Withholding the attestation is what actually stops the packet; this only leaves the\r\n /// audit trail. It is therefore best-effort by design — the worker treats a failure\r\n /// here as a lost record, never as a failure to enforce.\r\n function recordVerdict(\r\n bytes32 payloadHash,\r\n uint8 action,\r\n uint16 score,\r\n uint256 reasonMask,\r\n bytes32 evidenceHash\r\n ) external onlyOperator {\r\n if (action > ACTION_BLOCK) revert UnknownAction(action);\r\n if (action == ACTION_ALLOW) revert AllowNotSeparatelyRecorded();\r\n emit RiskVerdict(payloadHash, action, score, reasonMask, evidenceHash);\r\n }\r\n\r\n /// @notice Clear a packet the worker withheld for manual review.\r\n /// @dev Emits only; no storage. The worker observes `PacketApproved` and releases the\r\n /// packet from its local deferred queue. Approval is a human override of a risk\r\n /// verdict, so it is separated from the operator key by design — a compromised or\r\n /// buggy worker cannot approve the packets it chose to hold.\r\n function approvePacket(bytes32 payloadHash) external onlyOwner {\r\n emit PacketApproved(payloadHash, msg.sender);\r\n }\r\n\r\n function setOperator(address _operator) external onlyOwner {\r\n require(_operator != address(0), \"zero operator\");\r\n operator = _operator;\r\n emit OperatorSet(_operator);\r\n }\r\n\r\n function setReceiveUln(address _receiveUln) external onlyOwner {\r\n require(_receiveUln != address(0), \"zero receiveUln\");\r\n receiveUln = _receiveUln;\r\n emit ReceiveUlnSet(_receiveUln);\r\n }\r\n\r\n function setFee(uint256 _fee) external onlyOwner {\r\n fee = _fee;\r\n emit FeeSet(_fee);\r\n }\r\n\r\n function withdraw(address payable _to) external onlyOwner {\r\n (bool ok, ) = _to.call{ value: address(this).balance }(\"\");\r\n require(ok, \"withdraw failed\");\r\n }\r\n}\r\n" + }, + "contracts/mocks/ReceiveUlnMock.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.22;\n\n/// @notice Minimal stand-in for ReceiveUln302: records what `verify` was called with so tests\n/// can assert the DVN forwarded the attestation faithfully.\ncontract ReceiveUlnMock {\n bytes public lastHeader;\n bytes32 public lastPayloadHash;\n uint64 public lastConfirmations;\n uint256 public calls;\n\n function verify(bytes calldata _header, bytes32 _payloadHash, uint64 _confirmations) external {\n lastHeader = _header;\n lastPayloadHash = _payloadHash;\n lastConfirmations = _confirmations;\n calls++;\n }\n}\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "evmVersion": "paris", + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + "devdoc", + "userdoc", + "storageLayout", + "evm.gasEstimates" + ], + "": [ + "ast" + ] + } + }, + "metadata": { + "useLiteralContent": true + } + } +} \ No newline at end of file diff --git a/deployments/base-sepolia/solcInputs/8088d7b064191499b181ffda0ed40a97.json b/deployments/base-sepolia/solcInputs/8088d7b064191499b181ffda0ed40a97.json new file mode 100644 index 0000000..b08c7d6 --- /dev/null +++ b/deployments/base-sepolia/solcInputs/8088d7b064191499b181ffda0ed40a97.json @@ -0,0 +1,156 @@ +{ + "language": "Solidity", + "sources": { + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\nimport { IMessageLibManager } from \"./IMessageLibManager.sol\";\nimport { IMessagingComposer } from \"./IMessagingComposer.sol\";\nimport { IMessagingChannel } from \"./IMessagingChannel.sol\";\nimport { IMessagingContext } from \"./IMessagingContext.sol\";\n\nstruct MessagingParams {\n uint32 dstEid;\n bytes32 receiver;\n bytes message;\n bytes options;\n bool payInLzToken;\n}\n\nstruct MessagingReceipt {\n bytes32 guid;\n uint64 nonce;\n MessagingFee fee;\n}\n\nstruct MessagingFee {\n uint256 nativeFee;\n uint256 lzTokenFee;\n}\n\nstruct Origin {\n uint32 srcEid;\n bytes32 sender;\n uint64 nonce;\n}\n\ninterface ILayerZeroEndpointV2 is IMessageLibManager, IMessagingComposer, IMessagingChannel, IMessagingContext {\n event PacketSent(bytes encodedPayload, bytes options, address sendLibrary);\n\n event PacketVerified(Origin origin, address receiver, bytes32 payloadHash);\n\n event PacketDelivered(Origin origin, address receiver);\n\n event LzReceiveAlert(\n address indexed receiver,\n address indexed executor,\n Origin origin,\n bytes32 guid,\n uint256 gas,\n uint256 value,\n bytes message,\n bytes extraData,\n bytes reason\n );\n\n event LzTokenSet(address token);\n\n event DelegateSet(address sender, address delegate);\n\n function quote(MessagingParams calldata _params, address _sender) external view returns (MessagingFee memory);\n\n function send(\n MessagingParams calldata _params,\n address _refundAddress\n ) external payable returns (MessagingReceipt memory);\n\n function verify(Origin calldata _origin, address _receiver, bytes32 _payloadHash) external;\n\n function verifiable(Origin calldata _origin, address _receiver) external view returns (bool);\n\n function initializable(Origin calldata _origin, address _receiver) external view returns (bool);\n\n function lzReceive(\n Origin calldata _origin,\n address _receiver,\n bytes32 _guid,\n bytes calldata _message,\n bytes calldata _extraData\n ) external payable;\n\n // oapp can burn messages partially by calling this function with its own business logic if messages are verified in order\n function clear(address _oapp, Origin calldata _origin, bytes32 _guid, bytes calldata _message) external;\n\n function setLzToken(address _lzToken) external;\n\n function lzToken() external view returns (address);\n\n function nativeToken() external view returns (address);\n\n function setDelegate(address _delegate) external;\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroReceiver.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\nimport { Origin } from \"./ILayerZeroEndpointV2.sol\";\n\ninterface ILayerZeroReceiver {\n function allowInitializePath(Origin calldata _origin) external view returns (bool);\n\n function nextNonce(uint32 _eid, bytes32 _sender) external view returns (uint64);\n\n function lzReceive(\n Origin calldata _origin,\n bytes32 _guid,\n bytes calldata _message,\n address _executor,\n bytes calldata _extraData\n ) external payable;\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLib.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\nimport { IERC165 } from \"@openzeppelin/contracts/utils/introspection/IERC165.sol\";\n\nimport { SetConfigParam } from \"./IMessageLibManager.sol\";\n\nenum MessageLibType {\n Send,\n Receive,\n SendAndReceive\n}\n\ninterface IMessageLib is IERC165 {\n function setConfig(address _oapp, SetConfigParam[] calldata _config) external;\n\n function getConfig(uint32 _eid, address _oapp, uint32 _configType) external view returns (bytes memory config);\n\n function isSupportedEid(uint32 _eid) external view returns (bool);\n\n // message libs of same major version are compatible\n function version() external view returns (uint64 major, uint8 minor, uint8 endpointVersion);\n\n function messageLibType() external view returns (MessageLibType);\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLibManager.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\nstruct SetConfigParam {\n uint32 eid;\n uint32 configType;\n bytes config;\n}\n\ninterface IMessageLibManager {\n struct Timeout {\n address lib;\n uint256 expiry;\n }\n\n event LibraryRegistered(address newLib);\n event DefaultSendLibrarySet(uint32 eid, address newLib);\n event DefaultReceiveLibrarySet(uint32 eid, address newLib);\n event DefaultReceiveLibraryTimeoutSet(uint32 eid, address oldLib, uint256 expiry);\n event SendLibrarySet(address sender, uint32 eid, address newLib);\n event ReceiveLibrarySet(address receiver, uint32 eid, address newLib);\n event ReceiveLibraryTimeoutSet(address receiver, uint32 eid, address oldLib, uint256 timeout);\n\n function registerLibrary(address _lib) external;\n\n function isRegisteredLibrary(address _lib) external view returns (bool);\n\n function getRegisteredLibraries() external view returns (address[] memory);\n\n function setDefaultSendLibrary(uint32 _eid, address _newLib) external;\n\n function defaultSendLibrary(uint32 _eid) external view returns (address);\n\n function setDefaultReceiveLibrary(uint32 _eid, address _newLib, uint256 _gracePeriod) external;\n\n function defaultReceiveLibrary(uint32 _eid) external view returns (address);\n\n function setDefaultReceiveLibraryTimeout(uint32 _eid, address _lib, uint256 _expiry) external;\n\n function defaultReceiveLibraryTimeout(uint32 _eid) external view returns (address lib, uint256 expiry);\n\n function isSupportedEid(uint32 _eid) external view returns (bool);\n\n function isValidReceiveLibrary(address _receiver, uint32 _eid, address _lib) external view returns (bool);\n\n /// ------------------- OApp interfaces -------------------\n function setSendLibrary(address _oapp, uint32 _eid, address _newLib) external;\n\n function getSendLibrary(address _sender, uint32 _eid) external view returns (address lib);\n\n function isDefaultSendLibrary(address _sender, uint32 _eid) external view returns (bool);\n\n function setReceiveLibrary(address _oapp, uint32 _eid, address _newLib, uint256 _gracePeriod) external;\n\n function getReceiveLibrary(address _receiver, uint32 _eid) external view returns (address lib, bool isDefault);\n\n function setReceiveLibraryTimeout(address _oapp, uint32 _eid, address _lib, uint256 _expiry) external;\n\n function receiveLibraryTimeout(address _receiver, uint32 _eid) external view returns (address lib, uint256 expiry);\n\n function setConfig(address _oapp, address _lib, SetConfigParam[] calldata _params) external;\n\n function getConfig(\n address _oapp,\n address _lib,\n uint32 _eid,\n uint32 _configType\n ) external view returns (bytes memory config);\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingChannel.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\ninterface IMessagingChannel {\n event InboundNonceSkipped(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce);\n event PacketNilified(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce, bytes32 payloadHash);\n event PacketBurnt(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce, bytes32 payloadHash);\n\n function eid() external view returns (uint32);\n\n // this is an emergency function if a message cannot be verified for some reasons\n // required to provide _nextNonce to avoid race condition\n function skip(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce) external;\n\n function nilify(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes32 _payloadHash) external;\n\n function burn(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes32 _payloadHash) external;\n\n function nextGuid(address _sender, uint32 _dstEid, bytes32 _receiver) external view returns (bytes32);\n\n function inboundNonce(address _receiver, uint32 _srcEid, bytes32 _sender) external view returns (uint64);\n\n function outboundNonce(address _sender, uint32 _dstEid, bytes32 _receiver) external view returns (uint64);\n\n function inboundPayloadHash(\n address _receiver,\n uint32 _srcEid,\n bytes32 _sender,\n uint64 _nonce\n ) external view returns (bytes32);\n\n function lazyInboundNonce(address _receiver, uint32 _srcEid, bytes32 _sender) external view returns (uint64);\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingComposer.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\ninterface IMessagingComposer {\n event ComposeSent(address from, address to, bytes32 guid, uint16 index, bytes message);\n event ComposeDelivered(address from, address to, bytes32 guid, uint16 index);\n event LzComposeAlert(\n address indexed from,\n address indexed to,\n address indexed executor,\n bytes32 guid,\n uint16 index,\n uint256 gas,\n uint256 value,\n bytes message,\n bytes extraData,\n bytes reason\n );\n\n function composeQueue(\n address _from,\n address _to,\n bytes32 _guid,\n uint16 _index\n ) external view returns (bytes32 messageHash);\n\n function sendCompose(address _to, bytes32 _guid, uint16 _index, bytes calldata _message) external;\n\n function lzCompose(\n address _from,\n address _to,\n bytes32 _guid,\n uint16 _index,\n bytes calldata _message,\n bytes calldata _extraData\n ) external payable;\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingContext.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\ninterface IMessagingContext {\n function isSendingMessage() external view returns (bool);\n\n function getSendContext() external view returns (uint32 dstEid, address sender);\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ISendLib.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\nimport { MessagingFee } from \"./ILayerZeroEndpointV2.sol\";\nimport { IMessageLib } from \"./IMessageLib.sol\";\n\nstruct Packet {\n uint64 nonce;\n uint32 srcEid;\n address sender;\n uint32 dstEid;\n bytes32 receiver;\n bytes32 guid;\n bytes message;\n}\n\ninterface ISendLib is IMessageLib {\n function send(\n Packet calldata _packet,\n bytes calldata _options,\n bool _payInLzToken\n ) external returns (MessagingFee memory, bytes memory encodedPacket);\n\n function quote(\n Packet calldata _packet,\n bytes calldata _options,\n bool _payInLzToken\n ) external view returns (MessagingFee memory);\n\n function setTreasury(address _treasury) external;\n\n function withdrawFee(address _to, uint256 _amount) external;\n\n function withdrawLzTokenFee(address _lzToken, address _to, uint256 _amount) external;\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/AddressCast.sol": { + "content": "// SPDX-License-Identifier: LZBL-1.2\n\npragma solidity ^0.8.20;\n\nlibrary AddressCast {\n error AddressCast_InvalidSizeForAddress();\n error AddressCast_InvalidAddress();\n\n function toBytes32(bytes calldata _addressBytes) internal pure returns (bytes32 result) {\n if (_addressBytes.length > 32) revert AddressCast_InvalidAddress();\n result = bytes32(_addressBytes);\n unchecked {\n uint256 offset = 32 - _addressBytes.length;\n result = result >> (offset * 8);\n }\n }\n\n function toBytes32(address _address) internal pure returns (bytes32 result) {\n result = bytes32(uint256(uint160(_address)));\n }\n\n function toBytes(bytes32 _addressBytes32, uint256 _size) internal pure returns (bytes memory result) {\n if (_size == 0 || _size > 32) revert AddressCast_InvalidSizeForAddress();\n result = new bytes(_size);\n unchecked {\n uint256 offset = 256 - _size * 8;\n assembly {\n mstore(add(result, 32), shl(offset, _addressBytes32))\n }\n }\n }\n\n function toAddress(bytes32 _addressBytes32) internal pure returns (address result) {\n result = address(uint160(uint256(_addressBytes32)));\n }\n\n function toAddress(bytes calldata _addressBytes) internal pure returns (address result) {\n if (_addressBytes.length != 20) revert AddressCast_InvalidAddress();\n result = address(bytes20(_addressBytes));\n }\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.sol": { + "content": "// SPDX-License-Identifier: LZBL-1.2\n\npragma solidity ^0.8.20;\n\nimport { Packet } from \"../../interfaces/ISendLib.sol\";\nimport { AddressCast } from \"../../libs/AddressCast.sol\";\n\nlibrary PacketV1Codec {\n using AddressCast for address;\n using AddressCast for bytes32;\n\n uint8 internal constant PACKET_VERSION = 1;\n\n // header (version + nonce + path)\n // version\n uint256 private constant PACKET_VERSION_OFFSET = 0;\n // nonce\n uint256 private constant NONCE_OFFSET = 1;\n // path\n uint256 private constant SRC_EID_OFFSET = 9;\n uint256 private constant SENDER_OFFSET = 13;\n uint256 private constant DST_EID_OFFSET = 45;\n uint256 private constant RECEIVER_OFFSET = 49;\n // payload (guid + message)\n uint256 private constant GUID_OFFSET = 81; // keccak256(nonce + path)\n uint256 private constant MESSAGE_OFFSET = 113;\n\n function encode(Packet memory _packet) internal pure returns (bytes memory encodedPacket) {\n encodedPacket = abi.encodePacked(\n PACKET_VERSION,\n _packet.nonce,\n _packet.srcEid,\n _packet.sender.toBytes32(),\n _packet.dstEid,\n _packet.receiver,\n _packet.guid,\n _packet.message\n );\n }\n\n function encodePacketHeader(Packet memory _packet) internal pure returns (bytes memory) {\n return\n abi.encodePacked(\n PACKET_VERSION,\n _packet.nonce,\n _packet.srcEid,\n _packet.sender.toBytes32(),\n _packet.dstEid,\n _packet.receiver\n );\n }\n\n function encodePayload(Packet memory _packet) internal pure returns (bytes memory) {\n return abi.encodePacked(_packet.guid, _packet.message);\n }\n\n function header(bytes calldata _packet) internal pure returns (bytes calldata) {\n return _packet[0:GUID_OFFSET];\n }\n\n function version(bytes calldata _packet) internal pure returns (uint8) {\n return uint8(bytes1(_packet[PACKET_VERSION_OFFSET:NONCE_OFFSET]));\n }\n\n function nonce(bytes calldata _packet) internal pure returns (uint64) {\n return uint64(bytes8(_packet[NONCE_OFFSET:SRC_EID_OFFSET]));\n }\n\n function srcEid(bytes calldata _packet) internal pure returns (uint32) {\n return uint32(bytes4(_packet[SRC_EID_OFFSET:SENDER_OFFSET]));\n }\n\n function sender(bytes calldata _packet) internal pure returns (bytes32) {\n return bytes32(_packet[SENDER_OFFSET:DST_EID_OFFSET]);\n }\n\n function senderAddressB20(bytes calldata _packet) internal pure returns (address) {\n return sender(_packet).toAddress();\n }\n\n function dstEid(bytes calldata _packet) internal pure returns (uint32) {\n return uint32(bytes4(_packet[DST_EID_OFFSET:RECEIVER_OFFSET]));\n }\n\n function receiver(bytes calldata _packet) internal pure returns (bytes32) {\n return bytes32(_packet[RECEIVER_OFFSET:GUID_OFFSET]);\n }\n\n function receiverB20(bytes calldata _packet) internal pure returns (address) {\n return receiver(_packet).toAddress();\n }\n\n function guid(bytes calldata _packet) internal pure returns (bytes32) {\n return bytes32(_packet[GUID_OFFSET:MESSAGE_OFFSET]);\n }\n\n function message(bytes calldata _packet) internal pure returns (bytes calldata) {\n return bytes(_packet[MESSAGE_OFFSET:]);\n }\n\n function payload(bytes calldata _packet) internal pure returns (bytes calldata) {\n return bytes(_packet[GUID_OFFSET:]);\n }\n\n function payloadHash(bytes calldata _packet) internal pure returns (bytes32) {\n return keccak256(payload(_packet));\n }\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppCore.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { ILayerZeroEndpointV2 } from \"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\";\n\n/**\n * @title IOAppCore\n */\ninterface IOAppCore {\n // Custom error messages\n error OnlyPeer(uint32 eid, bytes32 sender);\n error NoPeer(uint32 eid);\n error InvalidEndpointCall();\n error InvalidDelegate();\n\n // Event emitted when a peer (OApp) is set for a corresponding endpoint\n event PeerSet(uint32 eid, bytes32 peer);\n\n /**\n * @notice Retrieves the OApp version information.\n * @return senderVersion The version of the OAppSender.sol contract.\n * @return receiverVersion The version of the OAppReceiver.sol contract.\n */\n function oAppVersion() external view returns (uint64 senderVersion, uint64 receiverVersion);\n\n /**\n * @notice Retrieves the LayerZero endpoint associated with the OApp.\n * @return iEndpoint The LayerZero endpoint as an interface.\n */\n function endpoint() external view returns (ILayerZeroEndpointV2 iEndpoint);\n\n /**\n * @notice Retrieves the peer (OApp) associated with a corresponding endpoint.\n * @param _eid The endpoint ID.\n * @return peer The peer address (OApp instance) associated with the corresponding endpoint.\n */\n function peers(uint32 _eid) external view returns (bytes32 peer);\n\n /**\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\n * @param _eid The endpoint ID.\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\n */\n function setPeer(uint32 _eid, bytes32 _peer) external;\n\n /**\n * @notice Sets the delegate address for the OApp Core.\n * @param _delegate The address of the delegate to be set.\n */\n function setDelegate(address _delegate) external;\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppMsgInspector.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\n/**\n * @title IOAppMsgInspector\n * @dev Interface for the OApp Message Inspector, allowing examination of message and options contents.\n */\ninterface IOAppMsgInspector {\n // Custom error message for inspection failure\n error InspectionFailed(bytes message, bytes options);\n\n /**\n * @notice Allows the inspector to examine LayerZero message contents and optionally throw a revert if invalid.\n * @param _message The message payload to be inspected.\n * @param _options Additional options or parameters for inspection.\n * @return valid A boolean indicating whether the inspection passed (true) or failed (false).\n *\n * @dev Optionally done as a revert, OR use the boolean provided to handle the failure.\n */\n function inspect(bytes calldata _message, bytes calldata _options) external view returns (bool valid);\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppOptionsType3.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Struct representing enforced option parameters.\n */\nstruct EnforcedOptionParam {\n uint32 eid; // Endpoint ID\n uint16 msgType; // Message Type\n bytes options; // Additional options\n}\n\n/**\n * @title IOAppOptionsType3\n * @dev Interface for the OApp with Type 3 Options, allowing the setting and combining of enforced options.\n */\ninterface IOAppOptionsType3 {\n // Custom error message for invalid options\n error InvalidOptions(bytes options);\n\n // Event emitted when enforced options are set\n event EnforcedOptionSet(EnforcedOptionParam[] _enforcedOptions);\n\n /**\n * @notice Sets enforced options for specific endpoint and message type combinations.\n * @param _enforcedOptions An array of EnforcedOptionParam structures specifying enforced options.\n */\n function setEnforcedOptions(EnforcedOptionParam[] calldata _enforcedOptions) external;\n\n /**\n * @notice Combines options for a given endpoint and message type.\n * @param _eid The endpoint ID.\n * @param _msgType The OApp message type.\n * @param _extraOptions Additional options passed by the caller.\n * @return options The combination of caller specified options AND enforced options.\n */\n function combineOptions(\n uint32 _eid,\n uint16 _msgType,\n bytes calldata _extraOptions\n ) external view returns (bytes memory options);\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppReceiver.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.20;\n\nimport { ILayerZeroReceiver, Origin } from \"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroReceiver.sol\";\n\ninterface IOAppReceiver is ILayerZeroReceiver {\n /**\n * @notice Indicates whether an address is an approved composeMsg sender to the Endpoint.\n * @param _origin The origin information containing the source endpoint and sender address.\n * - srcEid: The source chain endpoint ID.\n * - sender: The sender address on the src chain.\n * - nonce: The nonce of the message.\n * @param _message The lzReceive payload.\n * @param _sender The sender address.\n * @return isSender Is a valid sender.\n *\n * @dev Applications can optionally choose to implement a separate composeMsg sender that is NOT the bridging layer.\n * @dev The default sender IS the OAppReceiver implementer.\n */\n function isComposeMsgSender(\n Origin calldata _origin,\n bytes calldata _message,\n address _sender\n ) external view returns (bool isSender);\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { Ownable } from \"@openzeppelin/contracts/access/Ownable.sol\";\nimport { IOAppOptionsType3, EnforcedOptionParam } from \"../interfaces/IOAppOptionsType3.sol\";\n\n/**\n * @title OAppOptionsType3\n * @dev Abstract contract implementing the IOAppOptionsType3 interface with type 3 options.\n */\nabstract contract OAppOptionsType3 is IOAppOptionsType3, Ownable {\n uint16 internal constant OPTION_TYPE_3 = 3;\n\n // @dev The \"msgType\" should be defined in the child contract.\n mapping(uint32 eid => mapping(uint16 msgType => bytes enforcedOption)) public enforcedOptions;\n\n /**\n * @dev Sets the enforced options for specific endpoint and message type combinations.\n * @param _enforcedOptions An array of EnforcedOptionParam structures specifying enforced options.\n *\n * @dev Only the owner/admin of the OApp can call this function.\n * @dev Provides a way for the OApp to enforce things like paying for PreCrime, AND/OR minimum dst lzReceive gas amounts etc.\n * @dev These enforced options can vary as the potential options/execution on the remote may differ as per the msgType.\n * eg. Amount of lzReceive() gas necessary to deliver a lzCompose() message adds overhead you dont want to pay\n * if you are only making a standard LayerZero message ie. lzReceive() WITHOUT sendCompose().\n */\n function setEnforcedOptions(EnforcedOptionParam[] calldata _enforcedOptions) public virtual onlyOwner {\n _setEnforcedOptions(_enforcedOptions);\n }\n\n /**\n * @dev Sets the enforced options for specific endpoint and message type combinations.\n * @param _enforcedOptions An array of EnforcedOptionParam structures specifying enforced options.\n *\n * @dev Provides a way for the OApp to enforce things like paying for PreCrime, AND/OR minimum dst lzReceive gas amounts etc.\n * @dev These enforced options can vary as the potential options/execution on the remote may differ as per the msgType.\n * eg. Amount of lzReceive() gas necessary to deliver a lzCompose() message adds overhead you dont want to pay\n * if you are only making a standard LayerZero message ie. lzReceive() WITHOUT sendCompose().\n */\n function _setEnforcedOptions(EnforcedOptionParam[] memory _enforcedOptions) internal virtual {\n for (uint256 i = 0; i < _enforcedOptions.length; i++) {\n // @dev Enforced options are only available for optionType 3, as type 1 and 2 dont support combining.\n _assertOptionsType3(_enforcedOptions[i].options);\n enforcedOptions[_enforcedOptions[i].eid][_enforcedOptions[i].msgType] = _enforcedOptions[i].options;\n }\n\n emit EnforcedOptionSet(_enforcedOptions);\n }\n\n /**\n * @notice Combines options for a given endpoint and message type.\n * @param _eid The endpoint ID.\n * @param _msgType The OAPP message type.\n * @param _extraOptions Additional options passed by the caller.\n * @return options The combination of caller specified options AND enforced options.\n *\n * @dev If there is an enforced lzReceive option:\n * - {gasLimit: 200k, msg.value: 1 ether} AND a caller supplies a lzReceive option: {gasLimit: 100k, msg.value: 0.5 ether}\n * - The resulting options will be {gasLimit: 300k, msg.value: 1.5 ether} when the message is executed on the remote lzReceive() function.\n * @dev This presence of duplicated options is handled off-chain in the verifier/executor.\n */\n function combineOptions(\n uint32 _eid,\n uint16 _msgType,\n bytes calldata _extraOptions\n ) public view virtual returns (bytes memory) {\n bytes memory enforced = enforcedOptions[_eid][_msgType];\n\n // No enforced options, pass whatever the caller supplied, even if it's empty or legacy type 1/2 options.\n if (enforced.length == 0) return _extraOptions;\n\n // No caller options, return enforced\n if (_extraOptions.length == 0) return enforced;\n\n // @dev If caller provided _extraOptions, must be type 3 as its the ONLY type that can be combined.\n if (_extraOptions.length >= 2) {\n _assertOptionsType3(_extraOptions);\n // @dev Remove the first 2 bytes containing the type from the _extraOptions and combine with enforced.\n return bytes.concat(enforced, _extraOptions[2:]);\n }\n\n // No valid set of options was found.\n revert InvalidOptions(_extraOptions);\n }\n\n /**\n * @dev Internal function to assert that options are of type 3.\n * @param _options The options to be checked.\n */\n function _assertOptionsType3(bytes memory _options) internal pure virtual {\n uint16 optionsType;\n assembly {\n optionsType := mload(add(_options, 2))\n }\n if (optionsType != OPTION_TYPE_3) revert InvalidOptions(_options);\n }\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\n// @dev Import the 'MessagingFee' and 'MessagingReceipt' so it's exposed to OApp implementers\n// solhint-disable-next-line no-unused-import\nimport { OAppSender, MessagingFee, MessagingReceipt } from \"./OAppSender.sol\";\n// @dev Import the 'Origin' so it's exposed to OApp implementers\n// solhint-disable-next-line no-unused-import\nimport { OAppReceiver, Origin } from \"./OAppReceiver.sol\";\nimport { OAppCore } from \"./OAppCore.sol\";\n\n/**\n * @title OApp\n * @dev Abstract contract serving as the base for OApp implementation, combining OAppSender and OAppReceiver functionality.\n */\nabstract contract OApp is OAppSender, OAppReceiver {\n /**\n * @dev Constructor to initialize the OApp with the provided endpoint and owner.\n * @param _endpoint The address of the LOCAL LayerZero endpoint.\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\n */\n constructor(address _endpoint, address _delegate) OAppCore(_endpoint, _delegate) {}\n\n /**\n * @notice Retrieves the OApp version information.\n * @return senderVersion The version of the OAppSender.sol implementation.\n * @return receiverVersion The version of the OAppReceiver.sol implementation.\n */\n function oAppVersion()\n public\n pure\n virtual\n override(OAppSender, OAppReceiver)\n returns (uint64 senderVersion, uint64 receiverVersion)\n {\n return (SENDER_VERSION, RECEIVER_VERSION);\n }\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/OAppCore.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { Ownable } from \"@openzeppelin/contracts/access/Ownable.sol\";\nimport { IOAppCore, ILayerZeroEndpointV2 } from \"./interfaces/IOAppCore.sol\";\n\n/**\n * @title OAppCore\n * @dev Abstract contract implementing the IOAppCore interface with basic OApp configurations.\n */\nabstract contract OAppCore is IOAppCore, Ownable {\n // The LayerZero endpoint associated with the given OApp\n ILayerZeroEndpointV2 public immutable endpoint;\n\n // Mapping to store peers associated with corresponding endpoints\n mapping(uint32 eid => bytes32 peer) public peers;\n\n /**\n * @dev Constructor to initialize the OAppCore with the provided endpoint and delegate.\n * @param _endpoint The address of the LOCAL Layer Zero endpoint.\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\n *\n * @dev The delegate typically should be set as the owner of the contract.\n */\n constructor(address _endpoint, address _delegate) {\n endpoint = ILayerZeroEndpointV2(_endpoint);\n\n if (_delegate == address(0)) revert InvalidDelegate();\n endpoint.setDelegate(_delegate);\n }\n\n /**\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\n * @param _eid The endpoint ID.\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\n *\n * @dev Only the owner/admin of the OApp can call this function.\n * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp.\n * @dev Set this to bytes32(0) to remove the peer address.\n * @dev Peer is a bytes32 to accommodate non-evm chains.\n */\n function setPeer(uint32 _eid, bytes32 _peer) public virtual onlyOwner {\n _setPeer(_eid, _peer);\n }\n\n /**\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\n * @param _eid The endpoint ID.\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\n *\n * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp.\n * @dev Set this to bytes32(0) to remove the peer address.\n * @dev Peer is a bytes32 to accommodate non-evm chains.\n */\n function _setPeer(uint32 _eid, bytes32 _peer) internal virtual {\n peers[_eid] = _peer;\n emit PeerSet(_eid, _peer);\n }\n\n /**\n * @notice Internal function to get the peer address associated with a specific endpoint; reverts if NOT set.\n * ie. the peer is set to bytes32(0).\n * @param _eid The endpoint ID.\n * @return peer The address of the peer associated with the specified endpoint.\n */\n function _getPeerOrRevert(uint32 _eid) internal view virtual returns (bytes32) {\n bytes32 peer = peers[_eid];\n if (peer == bytes32(0)) revert NoPeer(_eid);\n return peer;\n }\n\n /**\n * @notice Sets the delegate address for the OApp.\n * @param _delegate The address of the delegate to be set.\n *\n * @dev Only the owner/admin of the OApp can call this function.\n * @dev Provides the ability for a delegate to set configs, on behalf of the OApp, directly on the Endpoint contract.\n */\n function setDelegate(address _delegate) public onlyOwner {\n endpoint.setDelegate(_delegate);\n }\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/OAppReceiver.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { IOAppReceiver, Origin } from \"./interfaces/IOAppReceiver.sol\";\nimport { OAppCore } from \"./OAppCore.sol\";\n\n/**\n * @title OAppReceiver\n * @dev Abstract contract implementing the ILayerZeroReceiver interface and extending OAppCore for OApp receivers.\n */\nabstract contract OAppReceiver is IOAppReceiver, OAppCore {\n // Custom error message for when the caller is not the registered endpoint/\n error OnlyEndpoint(address addr);\n\n // @dev The version of the OAppReceiver implementation.\n // @dev Version is bumped when changes are made to this contract.\n uint64 internal constant RECEIVER_VERSION = 2;\n\n /**\n * @notice Retrieves the OApp version information.\n * @return senderVersion The version of the OAppSender.sol contract.\n * @return receiverVersion The version of the OAppReceiver.sol contract.\n *\n * @dev Providing 0 as the default for OAppSender version. Indicates that the OAppSender is not implemented.\n * ie. this is a RECEIVE only OApp.\n * @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions.\n */\n function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) {\n return (0, RECEIVER_VERSION);\n }\n\n /**\n * @notice Indicates whether an address is an approved composeMsg sender to the Endpoint.\n * @dev _origin The origin information containing the source endpoint and sender address.\n * - srcEid: The source chain endpoint ID.\n * - sender: The sender address on the src chain.\n * - nonce: The nonce of the message.\n * @dev _message The lzReceive payload.\n * @param _sender The sender address.\n * @return isSender Is a valid sender.\n *\n * @dev Applications can optionally choose to implement separate composeMsg senders that are NOT the bridging layer.\n * @dev The default sender IS the OAppReceiver implementer.\n */\n function isComposeMsgSender(\n Origin calldata /*_origin*/,\n bytes calldata /*_message*/,\n address _sender\n ) public view virtual returns (bool) {\n return _sender == address(this);\n }\n\n /**\n * @notice Checks if the path initialization is allowed based on the provided origin.\n * @param origin The origin information containing the source endpoint and sender address.\n * @return Whether the path has been initialized.\n *\n * @dev This indicates to the endpoint that the OApp has enabled msgs for this particular path to be received.\n * @dev This defaults to assuming if a peer has been set, its initialized.\n * Can be overridden by the OApp if there is other logic to determine this.\n */\n function allowInitializePath(Origin calldata origin) public view virtual returns (bool) {\n return peers[origin.srcEid] == origin.sender;\n }\n\n /**\n * @notice Retrieves the next nonce for a given source endpoint and sender address.\n * @dev _srcEid The source endpoint ID.\n * @dev _sender The sender address.\n * @return nonce The next nonce.\n *\n * @dev The path nonce starts from 1. If 0 is returned it means that there is NO nonce ordered enforcement.\n * @dev Is required by the off-chain executor to determine the OApp expects msg execution is ordered.\n * @dev This is also enforced by the OApp.\n * @dev By default this is NOT enabled. ie. nextNonce is hardcoded to return 0.\n */\n function nextNonce(uint32 /*_srcEid*/, bytes32 /*_sender*/) public view virtual returns (uint64 nonce) {\n return 0;\n }\n\n /**\n * @dev Entry point for receiving messages or packets from the endpoint.\n * @param _origin The origin information containing the source endpoint and sender address.\n * - srcEid: The source chain endpoint ID.\n * - sender: The sender address on the src chain.\n * - nonce: The nonce of the message.\n * @param _guid The unique identifier for the received LayerZero message.\n * @param _message The payload of the received message.\n * @param _executor The address of the executor for the received message.\n * @param _extraData Additional arbitrary data provided by the corresponding executor.\n *\n * @dev Entry point for receiving msg/packet from the LayerZero endpoint.\n */\n function lzReceive(\n Origin calldata _origin,\n bytes32 _guid,\n bytes calldata _message,\n address _executor,\n bytes calldata _extraData\n ) public payable virtual {\n // Ensures that only the endpoint can attempt to lzReceive() messages to this OApp.\n if (address(endpoint) != msg.sender) revert OnlyEndpoint(msg.sender);\n\n // Ensure that the sender matches the expected peer for the source endpoint.\n if (_getPeerOrRevert(_origin.srcEid) != _origin.sender) revert OnlyPeer(_origin.srcEid, _origin.sender);\n\n // Call the internal OApp implementation of lzReceive.\n _lzReceive(_origin, _guid, _message, _executor, _extraData);\n }\n\n /**\n * @dev Internal function to implement lzReceive logic without needing to copy the basic parameter validation.\n */\n function _lzReceive(\n Origin calldata _origin,\n bytes32 _guid,\n bytes calldata _message,\n address _executor,\n bytes calldata _extraData\n ) internal virtual;\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/OAppSender.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { SafeERC20, IERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { MessagingParams, MessagingFee, MessagingReceipt } from \"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\";\nimport { OAppCore } from \"./OAppCore.sol\";\n\n/**\n * @title OAppSender\n * @dev Abstract contract implementing the OAppSender functionality for sending messages to a LayerZero endpoint.\n */\nabstract contract OAppSender is OAppCore {\n using SafeERC20 for IERC20;\n\n // Custom error messages\n error NotEnoughNative(uint256 msgValue);\n error LzTokenUnavailable();\n\n // @dev The version of the OAppSender implementation.\n // @dev Version is bumped when changes are made to this contract.\n uint64 internal constant SENDER_VERSION = 1;\n\n /**\n * @notice Retrieves the OApp version information.\n * @return senderVersion The version of the OAppSender.sol contract.\n * @return receiverVersion The version of the OAppReceiver.sol contract.\n *\n * @dev Providing 0 as the default for OAppReceiver version. Indicates that the OAppReceiver is not implemented.\n * ie. this is a SEND only OApp.\n * @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions\n */\n function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) {\n return (SENDER_VERSION, 0);\n }\n\n /**\n * @dev Internal function to interact with the LayerZero EndpointV2.quote() for fee calculation.\n * @param _dstEid The destination endpoint ID.\n * @param _message The message payload.\n * @param _options Additional options for the message.\n * @param _payInLzToken Flag indicating whether to pay the fee in LZ tokens.\n * @return fee The calculated MessagingFee for the message.\n * - nativeFee: The native fee for the message.\n * - lzTokenFee: The LZ token fee for the message.\n */\n function _quote(\n uint32 _dstEid,\n bytes memory _message,\n bytes memory _options,\n bool _payInLzToken\n ) internal view virtual returns (MessagingFee memory fee) {\n return\n endpoint.quote(\n MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _payInLzToken),\n address(this)\n );\n }\n\n /**\n * @dev Internal function to interact with the LayerZero EndpointV2.send() for sending a message.\n * @param _dstEid The destination endpoint ID.\n * @param _message The message payload.\n * @param _options Additional options for the message.\n * @param _fee The calculated LayerZero fee for the message.\n * - nativeFee: The native fee.\n * - lzTokenFee: The lzToken fee.\n * @param _refundAddress The address to receive any excess fee values sent to the endpoint.\n * @return receipt The receipt for the sent message.\n * - guid: The unique identifier for the sent message.\n * - nonce: The nonce of the sent message.\n * - fee: The LayerZero fee incurred for the message.\n */\n function _lzSend(\n uint32 _dstEid,\n bytes memory _message,\n bytes memory _options,\n MessagingFee memory _fee,\n address _refundAddress\n ) internal virtual returns (MessagingReceipt memory receipt) {\n // @dev Push corresponding fees to the endpoint, any excess is sent back to the _refundAddress from the endpoint.\n uint256 messageValue = _payNative(_fee.nativeFee);\n if (_fee.lzTokenFee > 0) _payLzToken(_fee.lzTokenFee);\n\n return\n // solhint-disable-next-line check-send-result\n endpoint.send{ value: messageValue }(\n MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _fee.lzTokenFee > 0),\n _refundAddress\n );\n }\n\n /**\n * @dev Internal function to pay the native fee associated with the message.\n * @param _nativeFee The native fee to be paid.\n * @return nativeFee The amount of native currency paid.\n *\n * @dev If the OApp needs to initiate MULTIPLE LayerZero messages in a single transaction,\n * this will need to be overridden because msg.value would contain multiple lzFees.\n * @dev Should be overridden in the event the LayerZero endpoint requires a different native currency.\n * @dev Some EVMs use an ERC20 as a method for paying transactions/gasFees.\n * @dev The endpoint is EITHER/OR, ie. it will NOT support both types of native payment at a time.\n */\n function _payNative(uint256 _nativeFee) internal virtual returns (uint256 nativeFee) {\n if (msg.value != _nativeFee) revert NotEnoughNative(msg.value);\n return _nativeFee;\n }\n\n /**\n * @dev Internal function to pay the LZ token fee associated with the message.\n * @param _lzTokenFee The LZ token fee to be paid.\n *\n * @dev If the caller is trying to pay in the specified lzToken, then the lzTokenFee is passed to the endpoint.\n * @dev Any excess sent, is passed back to the specified _refundAddress in the _lzSend().\n */\n function _payLzToken(uint256 _lzTokenFee) internal virtual {\n // @dev Cannot cache the token because it is not immutable in the endpoint.\n address lzToken = endpoint.lzToken();\n if (lzToken == address(0)) revert LzTokenUnavailable();\n\n // Pay LZ token fee by sending tokens to the endpoint.\n IERC20(lzToken).safeTransferFrom(msg.sender, address(endpoint), _lzTokenFee);\n }\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/precrime/interfaces/IOAppPreCrimeSimulator.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\n// @dev Import the Origin so it's exposed to OAppPreCrimeSimulator implementers.\n// solhint-disable-next-line no-unused-import\nimport { InboundPacket, Origin } from \"../libs/Packet.sol\";\n\n/**\n * @title IOAppPreCrimeSimulator Interface\n * @dev Interface for the preCrime simulation functionality in an OApp.\n */\ninterface IOAppPreCrimeSimulator {\n // @dev simulation result used in PreCrime implementation\n error SimulationResult(bytes result);\n error OnlySelf();\n\n /**\n * @dev Emitted when the preCrime contract address is set.\n * @param preCrimeAddress The address of the preCrime contract.\n */\n event PreCrimeSet(address preCrimeAddress);\n\n /**\n * @dev Retrieves the address of the preCrime contract implementation.\n * @return The address of the preCrime contract.\n */\n function preCrime() external view returns (address);\n\n /**\n * @dev Retrieves the address of the OApp contract.\n * @return The address of the OApp contract.\n */\n function oApp() external view returns (address);\n\n /**\n * @dev Sets the preCrime contract address.\n * @param _preCrime The address of the preCrime contract.\n */\n function setPreCrime(address _preCrime) external;\n\n /**\n * @dev Mocks receiving a packet, then reverts with a series of data to infer the state/result.\n * @param _packets An array of LayerZero InboundPacket objects representing received packets.\n */\n function lzReceiveAndRevert(InboundPacket[] calldata _packets) external payable;\n\n /**\n * @dev checks if the specified peer is considered 'trusted' by the OApp.\n * @param _eid The endpoint Id to check.\n * @param _peer The peer to check.\n * @return Whether the peer passed is considered 'trusted' by the OApp.\n */\n function isPeer(uint32 _eid, bytes32 _peer) external view returns (bool);\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/precrime/interfaces/IPreCrime.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\nstruct PreCrimePeer {\n uint32 eid;\n bytes32 preCrime;\n bytes32 oApp;\n}\n\n// TODO not done yet\ninterface IPreCrime {\n error OnlyOffChain();\n\n // for simulate()\n error PacketOversize(uint256 max, uint256 actual);\n error PacketUnsorted();\n error SimulationFailed(bytes reason);\n\n // for preCrime()\n error SimulationResultNotFound(uint32 eid);\n error InvalidSimulationResult(uint32 eid, bytes reason);\n error CrimeFound(bytes crime);\n\n function getConfig(bytes[] calldata _packets, uint256[] calldata _packetMsgValues) external returns (bytes memory);\n\n function simulate(\n bytes[] calldata _packets,\n uint256[] calldata _packetMsgValues\n ) external payable returns (bytes memory);\n\n function buildSimulationResult() external view returns (bytes memory);\n\n function preCrime(\n bytes[] calldata _packets,\n uint256[] calldata _packetMsgValues,\n bytes[] calldata _simulations\n ) external;\n\n function version() external view returns (uint64 major, uint8 minor);\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/precrime/libs/Packet.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { Origin } from \"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\";\nimport { PacketV1Codec } from \"@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.sol\";\n\n/**\n * @title InboundPacket\n * @dev Structure representing an inbound packet received by the contract.\n */\nstruct InboundPacket {\n Origin origin; // Origin information of the packet.\n uint32 dstEid; // Destination endpointId of the packet.\n address receiver; // Receiver address for the packet.\n bytes32 guid; // Unique identifier of the packet.\n uint256 value; // msg.value of the packet.\n address executor; // Executor address for the packet.\n bytes message; // Message payload of the packet.\n bytes extraData; // Additional arbitrary data for the packet.\n}\n\n/**\n * @title PacketDecoder\n * @dev Library for decoding LayerZero packets.\n */\nlibrary PacketDecoder {\n using PacketV1Codec for bytes;\n\n /**\n * @dev Decode an inbound packet from the given packet data.\n * @param _packet The packet data to decode.\n * @return packet An InboundPacket struct representing the decoded packet.\n */\n function decode(bytes calldata _packet) internal pure returns (InboundPacket memory packet) {\n packet.origin = Origin(_packet.srcEid(), _packet.sender(), _packet.nonce());\n packet.dstEid = _packet.dstEid();\n packet.receiver = _packet.receiverB20();\n packet.guid = _packet.guid();\n packet.message = _packet.message();\n }\n\n /**\n * @dev Decode multiple inbound packets from the given packet data and associated message values.\n * @param _packets An array of packet data to decode.\n * @param _packetMsgValues An array of associated message values for each packet.\n * @return packets An array of InboundPacket structs representing the decoded packets.\n */\n function decode(\n bytes[] calldata _packets,\n uint256[] memory _packetMsgValues\n ) internal pure returns (InboundPacket[] memory packets) {\n packets = new InboundPacket[](_packets.length);\n for (uint256 i = 0; i < _packets.length; i++) {\n bytes calldata packet = _packets[i];\n packets[i] = PacketDecoder.decode(packet);\n // @dev Allows the verifier to specify the msg.value that gets passed in lzReceive.\n packets[i].value = _packetMsgValues[i];\n }\n }\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/precrime/OAppPreCrimeSimulator.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { Ownable } from \"@openzeppelin/contracts/access/Ownable.sol\";\nimport { IPreCrime } from \"./interfaces/IPreCrime.sol\";\nimport { IOAppPreCrimeSimulator, InboundPacket, Origin } from \"./interfaces/IOAppPreCrimeSimulator.sol\";\n\n/**\n * @title OAppPreCrimeSimulator\n * @dev Abstract contract serving as the base for preCrime simulation functionality in an OApp.\n */\nabstract contract OAppPreCrimeSimulator is IOAppPreCrimeSimulator, Ownable {\n // The address of the preCrime implementation.\n address public preCrime;\n\n /**\n * @dev Retrieves the address of the OApp contract.\n * @return The address of the OApp contract.\n *\n * @dev The simulator contract is the base contract for the OApp by default.\n * @dev If the simulator is a separate contract, override this function.\n */\n function oApp() external view virtual returns (address) {\n return address(this);\n }\n\n /**\n * @dev Sets the preCrime contract address.\n * @param _preCrime The address of the preCrime contract.\n */\n function setPreCrime(address _preCrime) public virtual onlyOwner {\n preCrime = _preCrime;\n emit PreCrimeSet(_preCrime);\n }\n\n /**\n * @dev Interface for pre-crime simulations. Always reverts at the end with the simulation results.\n * @param _packets An array of InboundPacket objects representing received packets to be delivered.\n *\n * @dev WARNING: MUST revert at the end with the simulation results.\n * @dev Gives the preCrime implementation the ability to mock sending packets to the lzReceive function,\n * WITHOUT actually executing them.\n */\n function lzReceiveAndRevert(InboundPacket[] calldata _packets) public payable virtual {\n for (uint256 i = 0; i < _packets.length; i++) {\n InboundPacket calldata packet = _packets[i];\n\n // Ignore packets that are not from trusted peers.\n if (!isPeer(packet.origin.srcEid, packet.origin.sender)) continue;\n\n // @dev Because a verifier is calling this function, it doesnt have access to executor params:\n // - address _executor\n // - bytes calldata _extraData\n // preCrime will NOT work for OApps that rely on these two parameters inside of their _lzReceive().\n // They are instead stubbed to default values, address(0) and bytes(\"\")\n // @dev Calling this.lzReceiveSimulate removes ability for assembly return 0 callstack exit,\n // which would cause the revert to be ignored.\n this.lzReceiveSimulate{ value: packet.value }(\n packet.origin,\n packet.guid,\n packet.message,\n packet.executor,\n packet.extraData\n );\n }\n\n // @dev Revert with the simulation results. msg.sender must implement IPreCrime.buildSimulationResult().\n revert SimulationResult(IPreCrime(msg.sender).buildSimulationResult());\n }\n\n /**\n * @dev Is effectively an internal function because msg.sender must be address(this).\n * Allows resetting the call stack for 'internal' calls.\n * @param _origin The origin information containing the source endpoint and sender address.\n * - srcEid: The source chain endpoint ID.\n * - sender: The sender address on the src chain.\n * - nonce: The nonce of the message.\n * @param _guid The unique identifier of the packet.\n * @param _message The message payload of the packet.\n * @param _executor The executor address for the packet.\n * @param _extraData Additional data for the packet.\n */\n function lzReceiveSimulate(\n Origin calldata _origin,\n bytes32 _guid,\n bytes calldata _message,\n address _executor,\n bytes calldata _extraData\n ) external payable virtual {\n // @dev Ensure ONLY can be called 'internally'.\n if (msg.sender != address(this)) revert OnlySelf();\n _lzReceiveSimulate(_origin, _guid, _message, _executor, _extraData);\n }\n\n /**\n * @dev Internal function to handle the OAppPreCrimeSimulator simulated receive.\n * @param _origin The origin information.\n * - srcEid: The source chain endpoint ID.\n * - sender: The sender address from the src chain.\n * - nonce: The nonce of the LayerZero message.\n * @param _guid The GUID of the LayerZero message.\n * @param _message The LayerZero message.\n * @param _executor The address of the off-chain executor.\n * @param _extraData Arbitrary data passed by the msg executor.\n *\n * @dev Enables the preCrime simulator to mock sending lzReceive() messages,\n * routes the msg down from the OAppPreCrimeSimulator, and back up to the OAppReceiver.\n */\n function _lzReceiveSimulate(\n Origin calldata _origin,\n bytes32 _guid,\n bytes calldata _message,\n address _executor,\n bytes calldata _extraData\n ) internal virtual;\n\n /**\n * @dev checks if the specified peer is considered 'trusted' by the OApp.\n * @param _eid The endpoint Id to check.\n * @param _peer The peer to check.\n * @return Whether the peer passed is considered 'trusted' by the OApp.\n */\n function isPeer(uint32 _eid, bytes32 _peer) public view virtual returns (bool);\n}\n" + }, + "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { MessagingReceipt, MessagingFee } from \"@layerzerolabs/oapp-evm/contracts/oapp/OAppSender.sol\";\n\n/**\n * @dev Struct representing token parameters for the OFT send() operation.\n */\nstruct SendParam {\n uint32 dstEid; // Destination endpoint ID.\n bytes32 to; // Recipient address.\n uint256 amountLD; // Amount to send in local decimals.\n uint256 minAmountLD; // Minimum amount to send in local decimals.\n bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message.\n bytes composeMsg; // The composed message for the send() operation.\n bytes oftCmd; // The OFT command to be executed, unused in default OFT implementations.\n}\n\n/**\n * @dev Struct representing OFT limit information.\n * @dev These amounts can change dynamically and are up the specific oft implementation.\n */\nstruct OFTLimit {\n uint256 minAmountLD; // Minimum amount in local decimals that can be sent to the recipient.\n uint256 maxAmountLD; // Maximum amount in local decimals that can be sent to the recipient.\n}\n\n/**\n * @dev Struct representing OFT receipt information.\n */\nstruct OFTReceipt {\n uint256 amountSentLD; // Amount of tokens ACTUALLY debited from the sender in local decimals.\n // @dev In non-default implementations, the amountReceivedLD COULD differ from this value.\n uint256 amountReceivedLD; // Amount of tokens to be received on the remote side.\n}\n\n/**\n * @dev Struct representing OFT fee details.\n * @dev Future proof mechanism to provide a standardized way to communicate fees to things like a UI.\n */\nstruct OFTFeeDetail {\n int256 feeAmountLD; // Amount of the fee in local decimals.\n string description; // Description of the fee.\n}\n\n/**\n * @title IOFT\n * @dev Interface for the OftChain (OFT) token.\n * @dev Does not inherit ERC20 to accommodate usage by OFTAdapter as well.\n * @dev This specific interface ID is '0x02e49c2c'.\n */\ninterface IOFT {\n // Custom error messages\n error InvalidLocalDecimals();\n error SlippageExceeded(uint256 amountLD, uint256 minAmountLD);\n error AmountSDOverflowed(uint256 amountSD);\n\n // Events\n event OFTSent(\n bytes32 indexed guid, // GUID of the OFT message.\n uint32 dstEid, // Destination Endpoint ID.\n address indexed fromAddress, // Address of the sender on the src chain.\n uint256 amountSentLD, // Amount of tokens sent in local decimals.\n uint256 amountReceivedLD // Amount of tokens received in local decimals.\n );\n event OFTReceived(\n bytes32 indexed guid, // GUID of the OFT message.\n uint32 srcEid, // Source Endpoint ID.\n address indexed toAddress, // Address of the recipient on the dst chain.\n uint256 amountReceivedLD // Amount of tokens received in local decimals.\n );\n\n /**\n * @notice Retrieves interfaceID and the version of the OFT.\n * @return interfaceId The interface ID.\n * @return version The version.\n *\n * @dev interfaceId: This specific interface ID is '0x02e49c2c'.\n * @dev version: Indicates a cross-chain compatible msg encoding with other OFTs.\n * @dev If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented.\n * ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)\n */\n function oftVersion() external view returns (bytes4 interfaceId, uint64 version);\n\n /**\n * @notice Retrieves the address of the token associated with the OFT.\n * @return token The address of the ERC20 token implementation.\n */\n function token() external view returns (address);\n\n /**\n * @notice Indicates whether the OFT contract requires approval of the 'token()' to send.\n * @return requiresApproval Needs approval of the underlying token implementation.\n *\n * @dev Allows things like wallet implementers to determine integration requirements,\n * without understanding the underlying token implementation.\n */\n function approvalRequired() external view returns (bool);\n\n /**\n * @notice Retrieves the shared decimals of the OFT.\n * @return sharedDecimals The shared decimals of the OFT.\n */\n function sharedDecimals() external view returns (uint8);\n\n /**\n * @notice Provides the fee breakdown and settings data for an OFT. Unused in the default implementation.\n * @param _sendParam The parameters for the send operation.\n * @return limit The OFT limit information.\n * @return oftFeeDetails The details of OFT fees.\n * @return receipt The OFT receipt information.\n */\n function quoteOFT(\n SendParam calldata _sendParam\n ) external view returns (OFTLimit memory, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory);\n\n /**\n * @notice Provides a quote for the send() operation.\n * @param _sendParam The parameters for the send() operation.\n * @param _payInLzToken Flag indicating whether the caller is paying in the LZ token.\n * @return fee The calculated LayerZero messaging fee from the send() operation.\n *\n * @dev MessagingFee: LayerZero msg fee\n * - nativeFee: The native fee.\n * - lzTokenFee: The lzToken fee.\n */\n function quoteSend(SendParam calldata _sendParam, bool _payInLzToken) external view returns (MessagingFee memory);\n\n /**\n * @notice Executes the send() operation.\n * @param _sendParam The parameters for the send operation.\n * @param _fee The fee information supplied by the caller.\n * - nativeFee: The native fee.\n * - lzTokenFee: The lzToken fee.\n * @param _refundAddress The address to receive any excess funds from fees etc. on the src.\n * @return receipt The LayerZero messaging receipt from the send() operation.\n * @return oftReceipt The OFT receipt information.\n *\n * @dev MessagingReceipt: LayerZero msg receipt\n * - guid: The unique identifier for the sent message.\n * - nonce: The nonce of the sent message.\n * - fee: The LayerZero fee incurred for the message.\n */\n function send(\n SendParam calldata _sendParam,\n MessagingFee calldata _fee,\n address _refundAddress\n ) external payable returns (MessagingReceipt memory, OFTReceipt memory);\n}\n" + }, + "@layerzerolabs/oft-evm/contracts/libs/OFTComposeMsgCodec.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nlibrary OFTComposeMsgCodec {\n // Offset constants for decoding composed messages\n uint8 private constant NONCE_OFFSET = 8;\n uint8 private constant SRC_EID_OFFSET = 12;\n uint8 private constant AMOUNT_LD_OFFSET = 44;\n uint8 private constant COMPOSE_FROM_OFFSET = 76;\n\n /**\n * @dev Encodes a OFT composed message.\n * @param _nonce The nonce value.\n * @param _srcEid The source endpoint ID.\n * @param _amountLD The amount in local decimals.\n * @param _composeMsg The composed message.\n * @return _msg The encoded Composed message.\n */\n function encode(\n uint64 _nonce,\n uint32 _srcEid,\n uint256 _amountLD,\n bytes memory _composeMsg // 0x[composeFrom][composeMsg]\n ) internal pure returns (bytes memory _msg) {\n _msg = abi.encodePacked(_nonce, _srcEid, _amountLD, _composeMsg);\n }\n\n /**\n * @dev Retrieves the nonce for the composed message.\n * @param _msg The message.\n * @return The nonce value.\n */\n function nonce(bytes calldata _msg) internal pure returns (uint64) {\n return uint64(bytes8(_msg[:NONCE_OFFSET]));\n }\n\n /**\n * @dev Retrieves the source endpoint ID for the composed message.\n * @param _msg The message.\n * @return The source endpoint ID.\n */\n function srcEid(bytes calldata _msg) internal pure returns (uint32) {\n return uint32(bytes4(_msg[NONCE_OFFSET:SRC_EID_OFFSET]));\n }\n\n /**\n * @dev Retrieves the amount in local decimals from the composed message.\n * @param _msg The message.\n * @return The amount in local decimals.\n */\n function amountLD(bytes calldata _msg) internal pure returns (uint256) {\n return uint256(bytes32(_msg[SRC_EID_OFFSET:AMOUNT_LD_OFFSET]));\n }\n\n /**\n * @dev Retrieves the composeFrom value from the composed message.\n * @param _msg The message.\n * @return The composeFrom value.\n */\n function composeFrom(bytes calldata _msg) internal pure returns (bytes32) {\n return bytes32(_msg[AMOUNT_LD_OFFSET:COMPOSE_FROM_OFFSET]);\n }\n\n /**\n * @dev Retrieves the composed message.\n * @param _msg The message.\n * @return The composed message.\n */\n function composeMsg(bytes calldata _msg) internal pure returns (bytes memory) {\n return _msg[COMPOSE_FROM_OFFSET:];\n }\n\n /**\n * @dev Converts an address to bytes32.\n * @param _addr The address to convert.\n * @return The bytes32 representation of the address.\n */\n function addressToBytes32(address _addr) internal pure returns (bytes32) {\n return bytes32(uint256(uint160(_addr)));\n }\n\n /**\n * @dev Converts bytes32 to an address.\n * @param _b The bytes32 value to convert.\n * @return The address representation of bytes32.\n */\n function bytes32ToAddress(bytes32 _b) internal pure returns (address) {\n return address(uint160(uint256(_b)));\n }\n}\n" + }, + "@layerzerolabs/oft-evm/contracts/libs/OFTMsgCodec.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nlibrary OFTMsgCodec {\n // Offset constants for encoding and decoding OFT messages\n uint8 private constant SEND_TO_OFFSET = 32;\n uint8 private constant SEND_AMOUNT_SD_OFFSET = 40;\n\n /**\n * @dev Encodes an OFT LayerZero message.\n * @param _sendTo The recipient address.\n * @param _amountShared The amount in shared decimals.\n * @param _composeMsg The composed message.\n * @return _msg The encoded message.\n * @return hasCompose A boolean indicating whether the message has a composed payload.\n */\n function encode(\n bytes32 _sendTo,\n uint64 _amountShared,\n bytes memory _composeMsg\n ) internal view returns (bytes memory _msg, bool hasCompose) {\n hasCompose = _composeMsg.length > 0;\n // @dev Remote chains will want to know the composed function caller ie. msg.sender on the src.\n _msg = hasCompose\n ? abi.encodePacked(_sendTo, _amountShared, addressToBytes32(msg.sender), _composeMsg)\n : abi.encodePacked(_sendTo, _amountShared);\n }\n\n /**\n * @dev Checks if the OFT message is composed.\n * @param _msg The OFT message.\n * @return A boolean indicating whether the message is composed.\n */\n function isComposed(bytes calldata _msg) internal pure returns (bool) {\n return _msg.length > SEND_AMOUNT_SD_OFFSET;\n }\n\n /**\n * @dev Retrieves the recipient address from the OFT message.\n * @param _msg The OFT message.\n * @return The recipient address.\n */\n function sendTo(bytes calldata _msg) internal pure returns (bytes32) {\n return bytes32(_msg[:SEND_TO_OFFSET]);\n }\n\n /**\n * @dev Retrieves the amount in shared decimals from the OFT message.\n * @param _msg The OFT message.\n * @return The amount in shared decimals.\n */\n function amountSD(bytes calldata _msg) internal pure returns (uint64) {\n return uint64(bytes8(_msg[SEND_TO_OFFSET:SEND_AMOUNT_SD_OFFSET]));\n }\n\n /**\n * @dev Retrieves the composed message from the OFT message.\n * @param _msg The OFT message.\n * @return The composed message.\n */\n function composeMsg(bytes calldata _msg) internal pure returns (bytes memory) {\n return _msg[SEND_AMOUNT_SD_OFFSET:];\n }\n\n /**\n * @dev Converts an address to bytes32.\n * @param _addr The address to convert.\n * @return The bytes32 representation of the address.\n */\n function addressToBytes32(address _addr) internal pure returns (bytes32) {\n return bytes32(uint256(uint160(_addr)));\n }\n\n /**\n * @dev Converts bytes32 to an address.\n * @param _b The bytes32 value to convert.\n * @return The address representation of bytes32.\n */\n function bytes32ToAddress(bytes32 _b) internal pure returns (address) {\n return address(uint160(uint256(_b)));\n }\n}\n" + }, + "@layerzerolabs/oft-evm/contracts/OFT.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport { IOFT, OFTCore } from \"./OFTCore.sol\";\n\n/**\n * @title OFT Contract\n * @dev OFT is an ERC-20 token that extends the functionality of the OFTCore contract.\n */\nabstract contract OFT is OFTCore, ERC20 {\n /**\n * @dev Constructor for the OFT contract.\n * @param _name The name of the OFT.\n * @param _symbol The symbol of the OFT.\n * @param _lzEndpoint The LayerZero endpoint address.\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\n */\n constructor(\n string memory _name,\n string memory _symbol,\n address _lzEndpoint,\n address _delegate\n ) ERC20(_name, _symbol) OFTCore(decimals(), _lzEndpoint, _delegate) {}\n\n /**\n * @dev Retrieves the address of the underlying ERC20 implementation.\n * @return The address of the OFT token.\n *\n * @dev In the case of OFT, address(this) and erc20 are the same contract.\n */\n function token() public view returns (address) {\n return address(this);\n }\n\n /**\n * @notice Indicates whether the OFT contract requires approval of the 'token()' to send.\n * @return requiresApproval Needs approval of the underlying token implementation.\n *\n * @dev In the case of OFT where the contract IS the token, approval is NOT required.\n */\n function approvalRequired() external pure virtual returns (bool) {\n return false;\n }\n\n /**\n * @dev Burns tokens from the sender's specified balance.\n * @param _from The address to debit the tokens from.\n * @param _amountLD The amount of tokens to send in local decimals.\n * @param _minAmountLD The minimum amount to send in local decimals.\n * @param _dstEid The destination chain ID.\n * @return amountSentLD The amount sent in local decimals.\n * @return amountReceivedLD The amount received in local decimals on the remote.\n */\n function _debit(\n address _from,\n uint256 _amountLD,\n uint256 _minAmountLD,\n uint32 _dstEid\n ) internal virtual override returns (uint256 amountSentLD, uint256 amountReceivedLD) {\n (amountSentLD, amountReceivedLD) = _debitView(_amountLD, _minAmountLD, _dstEid);\n\n // @dev In NON-default OFT, amountSentLD could be 100, with a 10% fee, the amountReceivedLD amount is 90,\n // therefore amountSentLD CAN differ from amountReceivedLD.\n\n // @dev Default OFT burns on src.\n _burn(_from, amountSentLD);\n }\n\n /**\n * @dev Credits tokens to the specified address.\n * @param _to The address to credit the tokens to.\n * @param _amountLD The amount of tokens to credit in local decimals.\n * @dev _srcEid The source chain ID.\n * @return amountReceivedLD The amount of tokens ACTUALLY received in local decimals.\n */\n function _credit(\n address _to,\n uint256 _amountLD,\n uint32 /*_srcEid*/\n ) internal virtual override returns (uint256 amountReceivedLD) {\n if (_to == address(0x0)) _to = address(0xdead); // _mint(...) does not support address(0x0)\n // @dev Default OFT mints on dst.\n _mint(_to, _amountLD);\n // @dev In the case of NON-default OFT, the _amountLD MIGHT not be == amountReceivedLD.\n return _amountLD;\n }\n}\n" + }, + "@layerzerolabs/oft-evm/contracts/OFTCore.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\nimport { OApp, Origin } from \"@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol\";\nimport { OAppOptionsType3 } from \"@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol\";\nimport { IOAppMsgInspector } from \"@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppMsgInspector.sol\";\n\nimport { OAppPreCrimeSimulator } from \"@layerzerolabs/oapp-evm/contracts/precrime/OAppPreCrimeSimulator.sol\";\n\nimport { IOFT, SendParam, OFTLimit, OFTReceipt, OFTFeeDetail, MessagingReceipt, MessagingFee } from \"./interfaces/IOFT.sol\";\nimport { OFTMsgCodec } from \"./libs/OFTMsgCodec.sol\";\nimport { OFTComposeMsgCodec } from \"./libs/OFTComposeMsgCodec.sol\";\n\n/**\n * @title OFTCore\n * @dev Abstract contract for the OftChain (OFT) token.\n */\nabstract contract OFTCore is IOFT, OApp, OAppPreCrimeSimulator, OAppOptionsType3 {\n using OFTMsgCodec for bytes;\n using OFTMsgCodec for bytes32;\n\n // @notice Provides a conversion rate when swapping between denominations of SD and LD\n // - shareDecimals == SD == shared Decimals\n // - localDecimals == LD == local decimals\n // @dev Considers that tokens have different decimal amounts on various chains.\n // @dev eg.\n // For a token\n // - locally with 4 decimals --> 1.2345 => uint(12345)\n // - remotely with 2 decimals --> 1.23 => uint(123)\n // - The conversion rate would be 10 ** (4 - 2) = 100\n // @dev If you want to send 1.2345 -> (uint 12345), you CANNOT represent that value on the remote,\n // you can only display 1.23 -> uint(123).\n // @dev To preserve the dust that would otherwise be lost on that conversion,\n // we need to unify a denomination that can be represented on ALL chains inside of the OFT mesh\n uint256 public immutable decimalConversionRate;\n\n // @notice Msg types that are used to identify the various OFT operations.\n // @dev This can be extended in child contracts for non-default oft operations\n // @dev These values are used in things like combineOptions() in OAppOptionsType3.sol.\n uint16 public constant SEND = 1;\n uint16 public constant SEND_AND_CALL = 2;\n\n // Address of an optional contract to inspect both 'message' and 'options'\n address public msgInspector;\n event MsgInspectorSet(address inspector);\n\n /**\n * @dev Constructor.\n * @param _localDecimals The decimals of the token on the local chain (this chain).\n * @param _endpoint The address of the LayerZero endpoint.\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\n */\n constructor(uint8 _localDecimals, address _endpoint, address _delegate) OApp(_endpoint, _delegate) {\n if (_localDecimals < sharedDecimals()) revert InvalidLocalDecimals();\n decimalConversionRate = 10 ** (_localDecimals - sharedDecimals());\n }\n\n /**\n * @notice Retrieves interfaceID and the version of the OFT.\n * @return interfaceId The interface ID.\n * @return version The version.\n *\n * @dev interfaceId: This specific interface ID is '0x02e49c2c'.\n * @dev version: Indicates a cross-chain compatible msg encoding with other OFTs.\n * @dev If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented.\n * ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)\n */\n function oftVersion() external pure virtual returns (bytes4 interfaceId, uint64 version) {\n return (type(IOFT).interfaceId, 1);\n }\n\n /**\n * @dev Retrieves the shared decimals of the OFT.\n * @return The shared decimals of the OFT.\n *\n * @dev Sets an implicit cap on the amount of tokens, over uint64.max() will need some sort of outbound cap / totalSupply cap\n * Lowest common decimal denominator between chains.\n * Defaults to 6 decimal places to provide up to 18,446,744,073,709.551615 units (max uint64).\n * For tokens exceeding this totalSupply(), they will need to override the sharedDecimals function with something smaller.\n * ie. 4 sharedDecimals would be 1,844,674,407,370,955.1615\n */\n function sharedDecimals() public view virtual returns (uint8) {\n return 6;\n }\n\n /**\n * @dev Sets the message inspector address for the OFT.\n * @param _msgInspector The address of the message inspector.\n *\n * @dev This is an optional contract that can be used to inspect both 'message' and 'options'.\n * @dev Set it to address(0) to disable it, or set it to a contract address to enable it.\n */\n function setMsgInspector(address _msgInspector) public virtual onlyOwner {\n msgInspector = _msgInspector;\n emit MsgInspectorSet(_msgInspector);\n }\n\n /**\n * @notice Provides the fee breakdown and settings data for an OFT. Unused in the default implementation.\n * @param _sendParam The parameters for the send operation.\n * @return oftLimit The OFT limit information.\n * @return oftFeeDetails The details of OFT fees.\n * @return oftReceipt The OFT receipt information.\n */\n function quoteOFT(\n SendParam calldata _sendParam\n )\n external\n view\n virtual\n returns (OFTLimit memory oftLimit, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory oftReceipt)\n {\n uint256 minAmountLD = 0; // Unused in the default implementation.\n uint256 maxAmountLD = IERC20(this.token()).totalSupply(); // Unused in the default implementation.\n oftLimit = OFTLimit(minAmountLD, maxAmountLD);\n\n // Unused in the default implementation; reserved for future complex fee details.\n oftFeeDetails = new OFTFeeDetail[](0);\n\n // @dev This is the same as the send() operation, but without the actual send.\n // - amountSentLD is the amount in local decimals that would be sent from the sender.\n // - amountReceivedLD is the amount in local decimals that will be credited to the recipient on the remote OFT instance.\n // @dev The amountSentLD MIGHT not equal the amount the user actually receives. HOWEVER, the default does.\n (uint256 amountSentLD, uint256 amountReceivedLD) = _debitView(\n _sendParam.amountLD,\n _sendParam.minAmountLD,\n _sendParam.dstEid\n );\n oftReceipt = OFTReceipt(amountSentLD, amountReceivedLD);\n }\n\n /**\n * @notice Provides a quote for the send() operation.\n * @param _sendParam The parameters for the send() operation.\n * @param _payInLzToken Flag indicating whether the caller is paying in the LZ token.\n * @return msgFee The calculated LayerZero messaging fee from the send() operation.\n *\n * @dev MessagingFee: LayerZero msg fee\n * - nativeFee: The native fee.\n * - lzTokenFee: The lzToken fee.\n */\n function quoteSend(\n SendParam calldata _sendParam,\n bool _payInLzToken\n ) external view virtual returns (MessagingFee memory msgFee) {\n // @dev mock the amount to receive, this is the same operation used in the send().\n // The quote is as similar as possible to the actual send() operation.\n (, uint256 amountReceivedLD) = _debitView(_sendParam.amountLD, _sendParam.minAmountLD, _sendParam.dstEid);\n\n // @dev Builds the options and OFT message to quote in the endpoint.\n (bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam, amountReceivedLD);\n\n // @dev Calculates the LayerZero fee for the send() operation.\n return _quote(_sendParam.dstEid, message, options, _payInLzToken);\n }\n\n /**\n * @dev Executes the send operation.\n * @param _sendParam The parameters for the send operation.\n * @param _fee The calculated fee for the send() operation.\n * - nativeFee: The native fee.\n * - lzTokenFee: The lzToken fee.\n * @param _refundAddress The address to receive any excess funds.\n * @return msgReceipt The receipt for the send operation.\n * @return oftReceipt The OFT receipt information.\n *\n * @dev MessagingReceipt: LayerZero msg receipt\n * - guid: The unique identifier for the sent message.\n * - nonce: The nonce of the sent message.\n * - fee: The LayerZero fee incurred for the message.\n */\n function send(\n SendParam calldata _sendParam,\n MessagingFee calldata _fee,\n address _refundAddress\n ) external payable virtual returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) {\n return _send(_sendParam, _fee, _refundAddress);\n }\n\n /**\n * @dev Internal function to execute the send operation.\n * @param _sendParam The parameters for the send operation.\n * @param _fee The calculated fee for the send() operation.\n * - nativeFee: The native fee.\n * - lzTokenFee: The lzToken fee.\n * @param _refundAddress The address to receive any excess funds.\n * @return msgReceipt The receipt for the send operation.\n * @return oftReceipt The OFT receipt information.\n *\n * @dev MessagingReceipt: LayerZero msg receipt\n * - guid: The unique identifier for the sent message.\n * - nonce: The nonce of the sent message.\n * - fee: The LayerZero fee incurred for the message.\n */\n function _send(\n SendParam calldata _sendParam,\n MessagingFee calldata _fee,\n address _refundAddress\n ) internal virtual returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) {\n // @dev Applies the token transfers regarding this send() operation.\n // - amountSentLD is the amount in local decimals that was ACTUALLY sent/debited from the sender.\n // - amountReceivedLD is the amount in local decimals that will be received/credited to the recipient on the remote OFT instance.\n (uint256 amountSentLD, uint256 amountReceivedLD) = _debit(\n msg.sender,\n _sendParam.amountLD,\n _sendParam.minAmountLD,\n _sendParam.dstEid\n );\n\n // @dev Builds the options and OFT message to quote in the endpoint.\n (bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam, amountReceivedLD);\n\n // @dev Sends the message to the LayerZero endpoint and returns the LayerZero msg receipt.\n msgReceipt = _lzSend(_sendParam.dstEid, message, options, _fee, _refundAddress);\n // @dev Formulate the OFT receipt.\n oftReceipt = OFTReceipt(amountSentLD, amountReceivedLD);\n\n emit OFTSent(msgReceipt.guid, _sendParam.dstEid, msg.sender, amountSentLD, amountReceivedLD);\n }\n\n /**\n * @dev Internal function to build the message and options.\n * @param _sendParam The parameters for the send() operation.\n * @param _amountLD The amount in local decimals.\n * @return message The encoded message.\n * @return options The encoded options.\n */\n function _buildMsgAndOptions(\n SendParam calldata _sendParam,\n uint256 _amountLD\n ) internal view virtual returns (bytes memory message, bytes memory options) {\n bool hasCompose;\n // @dev This generated message has the msg.sender encoded into the payload so the remote knows who the caller is.\n (message, hasCompose) = OFTMsgCodec.encode(\n _sendParam.to,\n _toSD(_amountLD),\n // @dev Must be include a non empty bytes if you want to compose, EVEN if you dont need it on the remote.\n // EVEN if you dont require an arbitrary payload to be sent... eg. '0x01'\n _sendParam.composeMsg\n );\n // @dev Change the msg type depending if its composed or not.\n uint16 msgType = hasCompose ? SEND_AND_CALL : SEND;\n // @dev Combine the callers _extraOptions with the enforced options via the OAppOptionsType3.\n options = combineOptions(_sendParam.dstEid, msgType, _sendParam.extraOptions);\n\n // @dev Optionally inspect the message and options depending if the OApp owner has set a msg inspector.\n // @dev If it fails inspection, needs to revert in the implementation. ie. does not rely on return boolean\n address inspector = msgInspector; // caches the msgInspector to avoid potential double storage read\n if (inspector != address(0)) IOAppMsgInspector(inspector).inspect(message, options);\n }\n\n /**\n * @dev Internal function to handle the receive on the LayerZero endpoint.\n * @param _origin The origin information.\n * - srcEid: The source chain endpoint ID.\n * - sender: The sender address from the src chain.\n * - nonce: The nonce of the LayerZero message.\n * @param _guid The unique identifier for the received LayerZero message.\n * @param _message The encoded message.\n * @dev _executor The address of the executor.\n * @dev _extraData Additional data.\n */\n function _lzReceive(\n Origin calldata _origin,\n bytes32 _guid,\n bytes calldata _message,\n address /*_executor*/, // @dev unused in the default implementation.\n bytes calldata /*_extraData*/ // @dev unused in the default implementation.\n ) internal virtual override {\n // @dev The src sending chain doesnt know the address length on this chain (potentially non-evm)\n // Thus everything is bytes32() encoded in flight.\n address toAddress = _message.sendTo().bytes32ToAddress();\n // @dev Credit the amountLD to the recipient and return the ACTUAL amount the recipient received in local decimals\n uint256 amountReceivedLD = _credit(toAddress, _toLD(_message.amountSD()), _origin.srcEid);\n\n if (_message.isComposed()) {\n // @dev Proprietary composeMsg format for the OFT.\n bytes memory composeMsg = OFTComposeMsgCodec.encode(\n _origin.nonce,\n _origin.srcEid,\n amountReceivedLD,\n _message.composeMsg()\n );\n\n // @dev Stores the lzCompose payload that will be executed in a separate tx.\n // Standardizes functionality for executing arbitrary contract invocation on some non-evm chains.\n // @dev The off-chain executor will listen and process the msg based on the src-chain-callers compose options passed.\n // @dev The index is used when a OApp needs to compose multiple msgs on lzReceive.\n // For default OFT implementation there is only 1 compose msg per lzReceive, thus its always 0.\n endpoint.sendCompose(toAddress, _guid, 0 /* the index of the composed message*/, composeMsg);\n }\n\n emit OFTReceived(_guid, _origin.srcEid, toAddress, amountReceivedLD);\n }\n\n /**\n * @dev Internal function to handle the OAppPreCrimeSimulator simulated receive.\n * @param _origin The origin information.\n * - srcEid: The source chain endpoint ID.\n * - sender: The sender address from the src chain.\n * - nonce: The nonce of the LayerZero message.\n * @param _guid The unique identifier for the received LayerZero message.\n * @param _message The LayerZero message.\n * @param _executor The address of the off-chain executor.\n * @param _extraData Arbitrary data passed by the msg executor.\n *\n * @dev Enables the preCrime simulator to mock sending lzReceive() messages,\n * routes the msg down from the OAppPreCrimeSimulator, and back up to the OAppReceiver.\n */\n function _lzReceiveSimulate(\n Origin calldata _origin,\n bytes32 _guid,\n bytes calldata _message,\n address _executor,\n bytes calldata _extraData\n ) internal virtual override {\n _lzReceive(_origin, _guid, _message, _executor, _extraData);\n }\n\n /**\n * @dev Check if the peer is considered 'trusted' by the OApp.\n * @param _eid The endpoint ID to check.\n * @param _peer The peer to check.\n * @return Whether the peer passed is considered 'trusted' by the OApp.\n *\n * @dev Enables OAppPreCrimeSimulator to check whether a potential Inbound Packet is from a trusted source.\n */\n function isPeer(uint32 _eid, bytes32 _peer) public view virtual override returns (bool) {\n return peers[_eid] == _peer;\n }\n\n /**\n * @dev Internal function to remove dust from the given local decimal amount.\n * @param _amountLD The amount in local decimals.\n * @return amountLD The amount after removing dust.\n *\n * @dev Prevents the loss of dust when moving amounts between chains with different decimals.\n * @dev eg. uint(123) with a conversion rate of 100 becomes uint(100).\n */\n function _removeDust(uint256 _amountLD) internal view virtual returns (uint256 amountLD) {\n return (_amountLD / decimalConversionRate) * decimalConversionRate;\n }\n\n /**\n * @dev Internal function to convert an amount from shared decimals into local decimals.\n * @param _amountSD The amount in shared decimals.\n * @return amountLD The amount in local decimals.\n */\n function _toLD(uint64 _amountSD) internal view virtual returns (uint256 amountLD) {\n return _amountSD * decimalConversionRate;\n }\n\n /**\n * @dev Internal function to convert an amount from local decimals into shared decimals.\n * @param _amountLD The amount in local decimals.\n * @return amountSD The amount in shared decimals.\n *\n * @dev Reverts if the _amountLD in shared decimals overflows uint64.\n * @dev eg. uint(2**64 + 123) with a conversion rate of 1 wraps around 2**64 to uint(123).\n */\n function _toSD(uint256 _amountLD) internal view virtual returns (uint64 amountSD) {\n uint256 _amountSD = _amountLD / decimalConversionRate;\n if (_amountSD > type(uint64).max) revert AmountSDOverflowed(_amountSD);\n return uint64(_amountSD);\n }\n\n /**\n * @dev Internal function to mock the amount mutation from a OFT debit() operation.\n * @param _amountLD The amount to send in local decimals.\n * @param _minAmountLD The minimum amount to send in local decimals.\n * @dev _dstEid The destination endpoint ID.\n * @return amountSentLD The amount sent, in local decimals.\n * @return amountReceivedLD The amount to be received on the remote chain, in local decimals.\n *\n * @dev This is where things like fees would be calculated and deducted from the amount to be received on the remote.\n */\n function _debitView(\n uint256 _amountLD,\n uint256 _minAmountLD,\n uint32 /*_dstEid*/\n ) internal view virtual returns (uint256 amountSentLD, uint256 amountReceivedLD) {\n // @dev Remove the dust so nothing is lost on the conversion between chains with different decimals for the token.\n amountSentLD = _removeDust(_amountLD);\n // @dev The amount to send is the same as amount received in the default implementation.\n amountReceivedLD = amountSentLD;\n\n // @dev Check for slippage.\n if (amountReceivedLD < _minAmountLD) {\n revert SlippageExceeded(amountReceivedLD, _minAmountLD);\n }\n }\n\n /**\n * @dev Internal function to perform a debit operation.\n * @param _from The address to debit.\n * @param _amountLD The amount to send in local decimals.\n * @param _minAmountLD The minimum amount to send in local decimals.\n * @param _dstEid The destination endpoint ID.\n * @return amountSentLD The amount sent in local decimals.\n * @return amountReceivedLD The amount received in local decimals on the remote.\n *\n * @dev Defined here but are intended to be overriden depending on the OFT implementation.\n * @dev Depending on OFT implementation the _amountLD could differ from the amountReceivedLD.\n */\n function _debit(\n address _from,\n uint256 _amountLD,\n uint256 _minAmountLD,\n uint32 _dstEid\n ) internal virtual returns (uint256 amountSentLD, uint256 amountReceivedLD);\n\n /**\n * @dev Internal function to perform a credit operation.\n * @param _to The address to credit.\n * @param _amountLD The amount to credit in local decimals.\n * @param _srcEid The source endpoint ID.\n * @return amountReceivedLD The amount ACTUALLY received in local decimals.\n *\n * @dev Defined here but are intended to be overriden depending on the OFT implementation.\n * @dev Depending on OFT implementation the _amountLD could differ from the amountReceivedLD.\n */\n function _credit(\n address _to,\n uint256 _amountLD,\n uint32 _srcEid\n ) internal virtual returns (uint256 amountReceivedLD);\n}\n" + }, + "@openzeppelin/contracts/access/Ownable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\n\npragma solidity ^0.8.20;\n\nimport {Context} from \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * The initial owner is set to the address provided by the deployer. This can\n * later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n /**\n * @dev The caller account is not authorized to perform an operation.\n */\n error OwnableUnauthorizedAccount(address account);\n\n /**\n * @dev The owner is not a valid owner account. (eg. `address(0)`)\n */\n error OwnableInvalidOwner(address owner);\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\n */\n constructor(address initialOwner) {\n if (initialOwner == address(0)) {\n revert OwnableInvalidOwner(address(0));\n }\n _transferOwnership(initialOwner);\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n if (owner() != _msgSender()) {\n revert OwnableUnauthorizedAccount(_msgSender());\n }\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby disabling any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n if (newOwner == address(0)) {\n revert OwnableInvalidOwner(address(0));\n }\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n" + }, + "@openzeppelin/contracts/interfaces/draft-IERC6093.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.5.0) (interfaces/draft-IERC6093.sol)\n\npragma solidity >=0.8.4;\n\n/**\n * @dev Standard ERC-20 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-20 tokens.\n */\ninterface IERC20Errors {\n /**\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n * @param balance Current balance for the interacting account.\n * @param needed Minimum amount required to perform a transfer.\n */\n error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);\n\n /**\n * @dev Indicates a failure with the token `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n */\n error ERC20InvalidSender(address sender);\n\n /**\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\n * @param receiver Address to which tokens are being transferred.\n */\n error ERC20InvalidReceiver(address receiver);\n\n /**\n * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.\n * @param spender Address that may be allowed to operate on tokens without being their owner.\n * @param allowance Amount of tokens a `spender` is allowed to operate with.\n * @param needed Minimum amount required to perform a transfer.\n */\n error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);\n\n /**\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n * @param approver Address initiating an approval operation.\n */\n error ERC20InvalidApprover(address approver);\n\n /**\n * @dev Indicates a failure with the `spender` to be approved. Used in approvals.\n * @param spender Address that may be allowed to operate on tokens without being their owner.\n */\n error ERC20InvalidSpender(address spender);\n}\n\n/**\n * @dev Standard ERC-721 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-721 tokens.\n */\ninterface IERC721Errors {\n /**\n * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-721.\n * Used in balance queries.\n * @param owner Address of the current owner of a token.\n */\n error ERC721InvalidOwner(address owner);\n\n /**\n * @dev Indicates a `tokenId` whose `owner` is the zero address.\n * @param tokenId Identifier number of a token.\n */\n error ERC721NonexistentToken(uint256 tokenId);\n\n /**\n * @dev Indicates an error related to the ownership over a particular token. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n * @param tokenId Identifier number of a token.\n * @param owner Address of the current owner of a token.\n */\n error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);\n\n /**\n * @dev Indicates a failure with the token `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n */\n error ERC721InvalidSender(address sender);\n\n /**\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\n * @param receiver Address to which tokens are being transferred.\n */\n error ERC721InvalidReceiver(address receiver);\n\n /**\n * @dev Indicates a failure with the `operator`’s approval. Used in transfers.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n * @param tokenId Identifier number of a token.\n */\n error ERC721InsufficientApproval(address operator, uint256 tokenId);\n\n /**\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n * @param approver Address initiating an approval operation.\n */\n error ERC721InvalidApprover(address approver);\n\n /**\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n */\n error ERC721InvalidOperator(address operator);\n}\n\n/**\n * @dev Standard ERC-1155 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-1155 tokens.\n */\ninterface IERC1155Errors {\n /**\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n * @param balance Current balance for the interacting account.\n * @param needed Minimum amount required to perform a transfer.\n * @param tokenId Identifier number of a token.\n */\n error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);\n\n /**\n * @dev Indicates a failure with the token `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n */\n error ERC1155InvalidSender(address sender);\n\n /**\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\n * @param receiver Address to which tokens are being transferred.\n */\n error ERC1155InvalidReceiver(address receiver);\n\n /**\n * @dev Indicates a failure with the `operator`’s approval. Used in transfers.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n * @param owner Address of the current owner of a token.\n */\n error ERC1155MissingApprovalForAll(address operator, address owner);\n\n /**\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n * @param approver Address initiating an approval operation.\n */\n error ERC1155InvalidApprover(address approver);\n\n /**\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n */\n error ERC1155InvalidOperator(address operator);\n\n /**\n * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.\n * Used in batch transfers.\n * @param idsLength Length of the array of token identifiers\n * @param valuesLength Length of the array of token amounts\n */\n error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);\n}\n" + }, + "@openzeppelin/contracts/interfaces/IERC1363.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1363.sol)\n\npragma solidity >=0.6.2;\n\nimport {IERC20} from \"./IERC20.sol\";\nimport {IERC165} from \"./IERC165.sol\";\n\n/**\n * @title IERC1363\n * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].\n *\n * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract\n * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.\n */\ninterface IERC1363 is IERC20, IERC165 {\n /*\n * Note: the ERC-165 identifier for this interface is 0xb0202a11.\n * 0xb0202a11 ===\n * bytes4(keccak256('transferAndCall(address,uint256)')) ^\n * bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^\n * bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^\n * bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^\n * bytes4(keccak256('approveAndCall(address,uint256)')) ^\n * bytes4(keccak256('approveAndCall(address,uint256,bytes)'))\n */\n\n /**\n * @dev Moves a `value` amount of tokens from the caller's account to `to`\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n * @param to The address which you want to transfer to.\n * @param value The amount of tokens to be transferred.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function transferAndCall(address to, uint256 value) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from the caller's account to `to`\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n * @param to The address which you want to transfer to.\n * @param value The amount of tokens to be transferred.\n * @param data Additional data with no specified format, sent in call to `to`.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n * @param from The address which you want to send tokens from.\n * @param to The address which you want to transfer to.\n * @param value The amount of tokens to be transferred.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function transferFromAndCall(address from, address to, uint256 value) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n * @param from The address which you want to send tokens from.\n * @param to The address which you want to transfer to.\n * @param value The amount of tokens to be transferred.\n * @param data Additional data with no specified format, sent in call to `to`.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);\n\n /**\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.\n * @param spender The address which will spend the funds.\n * @param value The amount of tokens to be spent.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function approveAndCall(address spender, uint256 value) external returns (bool);\n\n /**\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.\n * @param spender The address which will spend the funds.\n * @param value The amount of tokens to be spent.\n * @param data Additional data with no specified format, sent in call to `spender`.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);\n}\n" + }, + "@openzeppelin/contracts/interfaces/IERC165.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC165.sol)\n\npragma solidity >=0.4.16;\n\nimport {IERC165} from \"../utils/introspection/IERC165.sol\";\n" + }, + "@openzeppelin/contracts/interfaces/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC20.sol)\n\npragma solidity >=0.4.16;\n\nimport {IERC20} from \"../token/ERC20/IERC20.sol\";\n" + }, + "@openzeppelin/contracts/token/ERC20/ERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.5.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"./IERC20.sol\";\nimport {IERC20Metadata} from \"./extensions/IERC20Metadata.sol\";\nimport {Context} from \"../../utils/Context.sol\";\nimport {IERC20Errors} from \"../../interfaces/draft-IERC6093.sol\";\n\n/**\n * @dev Implementation of the {IERC20} interface.\n *\n * This implementation is agnostic to the way tokens are created. This means\n * that a supply mechanism has to be added in a derived contract using {_mint}.\n *\n * TIP: For a detailed writeup see our guide\n * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How\n * to implement supply mechanisms].\n *\n * The default value of {decimals} is 18. To change this, you should override\n * this function so it returns a different value.\n *\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\n * instead returning `false` on failure. This behavior is nonetheless\n * conventional and does not conflict with the expectations of ERC-20\n * applications.\n */\nabstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {\n mapping(address account => uint256) private _balances;\n\n mapping(address account => mapping(address spender => uint256)) private _allowances;\n\n uint256 private _totalSupply;\n\n string private _name;\n string private _symbol;\n\n /**\n * @dev Sets the values for {name} and {symbol}.\n *\n * Both values are immutable: they can only be set once during construction.\n */\n constructor(string memory name_, string memory symbol_) {\n _name = name_;\n _symbol = symbol_;\n }\n\n /**\n * @dev Returns the name of the token.\n */\n function name() public view virtual returns (string memory) {\n return _name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view virtual returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei. This is the default value returned by this function, unless\n * it's overridden.\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view virtual returns (uint8) {\n return 18;\n }\n\n /// @inheritdoc IERC20\n function totalSupply() public view virtual returns (uint256) {\n return _totalSupply;\n }\n\n /// @inheritdoc IERC20\n function balanceOf(address account) public view virtual returns (uint256) {\n return _balances[account];\n }\n\n /**\n * @dev See {IERC20-transfer}.\n *\n * Requirements:\n *\n * - `to` cannot be the zero address.\n * - the caller must have a balance of at least `value`.\n */\n function transfer(address to, uint256 value) public virtual returns (bool) {\n address owner = _msgSender();\n _transfer(owner, to, value);\n return true;\n }\n\n /// @inheritdoc IERC20\n function allowance(address owner, address spender) public view virtual returns (uint256) {\n return _allowances[owner][spender];\n }\n\n /**\n * @dev See {IERC20-approve}.\n *\n * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on\n * `transferFrom`. This is semantically equivalent to an infinite approval.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function approve(address spender, uint256 value) public virtual returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, value);\n return true;\n }\n\n /**\n * @dev See {IERC20-transferFrom}.\n *\n * Skips emitting an {Approval} event indicating an allowance update. This is not\n * required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].\n *\n * NOTE: Does not update the allowance if the current allowance\n * is the maximum `uint256`.\n *\n * Requirements:\n *\n * - `from` and `to` cannot be the zero address.\n * - `from` must have a balance of at least `value`.\n * - the caller must have allowance for ``from``'s tokens of at least\n * `value`.\n */\n function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {\n address spender = _msgSender();\n _spendAllowance(from, spender, value);\n _transfer(from, to, value);\n return true;\n }\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to`.\n *\n * This internal function is equivalent to {transfer}, and can be used to\n * e.g. implement automatic token fees, slashing mechanisms, etc.\n *\n * Emits a {Transfer} event.\n *\n * NOTE: This function is not virtual, {_update} should be overridden instead.\n */\n function _transfer(address from, address to, uint256 value) internal {\n if (from == address(0)) {\n revert ERC20InvalidSender(address(0));\n }\n if (to == address(0)) {\n revert ERC20InvalidReceiver(address(0));\n }\n _update(from, to, value);\n }\n\n /**\n * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`\n * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding\n * this function.\n *\n * Emits a {Transfer} event.\n */\n function _update(address from, address to, uint256 value) internal virtual {\n if (from == address(0)) {\n // Overflow check required: The rest of the code assumes that totalSupply never overflows\n _totalSupply += value;\n } else {\n uint256 fromBalance = _balances[from];\n if (fromBalance < value) {\n revert ERC20InsufficientBalance(from, fromBalance, value);\n }\n unchecked {\n // Overflow not possible: value <= fromBalance <= totalSupply.\n _balances[from] = fromBalance - value;\n }\n }\n\n if (to == address(0)) {\n unchecked {\n // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.\n _totalSupply -= value;\n }\n } else {\n unchecked {\n // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.\n _balances[to] += value;\n }\n }\n\n emit Transfer(from, to, value);\n }\n\n /**\n * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).\n * Relies on the `_update` mechanism\n *\n * Emits a {Transfer} event with `from` set to the zero address.\n *\n * NOTE: This function is not virtual, {_update} should be overridden instead.\n */\n function _mint(address account, uint256 value) internal {\n if (account == address(0)) {\n revert ERC20InvalidReceiver(address(0));\n }\n _update(address(0), account, value);\n }\n\n /**\n * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.\n * Relies on the `_update` mechanism.\n *\n * Emits a {Transfer} event with `to` set to the zero address.\n *\n * NOTE: This function is not virtual, {_update} should be overridden instead\n */\n function _burn(address account, uint256 value) internal {\n if (account == address(0)) {\n revert ERC20InvalidSender(address(0));\n }\n _update(account, address(0), value);\n }\n\n /**\n * @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.\n *\n * This internal function is equivalent to `approve`, and can be used to\n * e.g. set automatic allowances for certain subsystems, etc.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `owner` cannot be the zero address.\n * - `spender` cannot be the zero address.\n *\n * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.\n */\n function _approve(address owner, address spender, uint256 value) internal {\n _approve(owner, spender, value, true);\n }\n\n /**\n * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.\n *\n * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by\n * `_spendAllowance` during the `transferFrom` operation sets the flag to false. This saves gas by not emitting any\n * `Approval` event during `transferFrom` operations.\n *\n * Anyone who wishes to continue emitting `Approval` events on the `transferFrom` operation can force the flag to\n * true using the following override:\n *\n * ```solidity\n * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {\n * super._approve(owner, spender, value, true);\n * }\n * ```\n *\n * Requirements are the same as {_approve}.\n */\n function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {\n if (owner == address(0)) {\n revert ERC20InvalidApprover(address(0));\n }\n if (spender == address(0)) {\n revert ERC20InvalidSpender(address(0));\n }\n _allowances[owner][spender] = value;\n if (emitEvent) {\n emit Approval(owner, spender, value);\n }\n }\n\n /**\n * @dev Updates `owner`'s allowance for `spender` based on spent `value`.\n *\n * Does not update the allowance value in case of infinite allowance.\n * Revert if not enough allowance is available.\n *\n * Does not emit an {Approval} event.\n */\n function _spendAllowance(address owner, address spender, uint256 value) internal virtual {\n uint256 currentAllowance = allowance(owner, spender);\n if (currentAllowance < type(uint256).max) {\n if (currentAllowance < value) {\n revert ERC20InsufficientAllowance(spender, currentAllowance, value);\n }\n unchecked {\n _approve(owner, spender, currentAllowance - value, false);\n }\n }\n }\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/extensions/IERC20Metadata.sol)\n\npragma solidity >=0.6.2;\n\nimport {IERC20} from \"../IERC20.sol\";\n\n/**\n * @dev Interface for the optional metadata functions from the ERC-20 standard.\n */\ninterface IERC20Metadata is IERC20 {\n /**\n * @dev Returns the name of the token.\n */\n function name() external view returns (string memory);\n\n /**\n * @dev Returns the symbol of the token.\n */\n function symbol() external view returns (string memory);\n\n /**\n * @dev Returns the decimals places of the token.\n */\n function decimals() external view returns (uint8);\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)\n\npragma solidity >=0.4.16;\n\n/**\n * @dev Interface of the ERC-20 standard as defined in the ERC.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the value of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the value of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves a `value` amount of tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 value) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n * caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 value) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to` using the\n * allowance mechanism. `value` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address from, address to, uint256 value) external returns (bool);\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.5.0) (token/ERC20/utils/SafeERC20.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"../IERC20.sol\";\nimport {IERC1363} from \"../../../interfaces/IERC1363.sol\";\n\n/**\n * @title SafeERC20\n * @dev Wrappers around ERC-20 operations that throw on failure (when the token\n * contract returns false). Tokens that return no value (and instead revert or\n * throw on failure) are also supported, non-reverting calls are assumed to be\n * successful.\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\n */\nlibrary SafeERC20 {\n /**\n * @dev An operation with an ERC-20 token failed.\n */\n error SafeERC20FailedOperation(address token);\n\n /**\n * @dev Indicates a failed `decreaseAllowance` request.\n */\n error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);\n\n /**\n * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,\n * non-reverting calls are assumed to be successful.\n */\n function safeTransfer(IERC20 token, address to, uint256 value) internal {\n if (!_safeTransfer(token, to, value, true)) {\n revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the\n * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.\n */\n function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {\n if (!_safeTransferFrom(token, from, to, value, true)) {\n revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.\n */\n function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {\n return _safeTransfer(token, to, value, false);\n }\n\n /**\n * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.\n */\n function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {\n return _safeTransferFrom(token, from, to, value, false);\n }\n\n /**\n * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,\n * non-reverting calls are assumed to be successful.\n *\n * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the \"client\"\n * smart contract uses ERC-7674 to set temporary allowances, then the \"client\" smart contract should avoid using\n * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract\n * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.\n */\n function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {\n uint256 oldAllowance = token.allowance(address(this), spender);\n forceApprove(token, spender, oldAllowance + value);\n }\n\n /**\n * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no\n * value, non-reverting calls are assumed to be successful.\n *\n * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the \"client\"\n * smart contract uses ERC-7674 to set temporary allowances, then the \"client\" smart contract should avoid using\n * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract\n * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.\n */\n function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {\n unchecked {\n uint256 currentAllowance = token.allowance(address(this), spender);\n if (currentAllowance < requestedDecrease) {\n revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);\n }\n forceApprove(token, spender, currentAllowance - requestedDecrease);\n }\n }\n\n /**\n * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,\n * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval\n * to be set to zero before setting it to a non-zero value, such as USDT.\n *\n * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function\n * only sets the \"standard\" allowance. Any temporary allowance will remain active, in addition to the value being\n * set here.\n */\n function forceApprove(IERC20 token, address spender, uint256 value) internal {\n if (!_safeApprove(token, spender, value, false)) {\n if (!_safeApprove(token, spender, 0, true)) revert SafeERC20FailedOperation(address(token));\n if (!_safeApprove(token, spender, value, true)) revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no\n * code. This can be used to implement an {ERC721}-like safe transfer that relies on {ERC1363} checks when\n * targeting contracts.\n *\n * Reverts if the returned value is other than `true`.\n */\n function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {\n if (to.code.length == 0) {\n safeTransfer(token, to, value);\n } else if (!token.transferAndCall(to, value, data)) {\n revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target\n * has no code. This can be used to implement an {ERC721}-like safe transfer that relies on {ERC1363} checks when\n * targeting contracts.\n *\n * Reverts if the returned value is other than `true`.\n */\n function transferFromAndCallRelaxed(\n IERC1363 token,\n address from,\n address to,\n uint256 value,\n bytes memory data\n ) internal {\n if (to.code.length == 0) {\n safeTransferFrom(token, from, to, value);\n } else if (!token.transferFromAndCall(from, to, value, data)) {\n revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no\n * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when\n * targeting contracts.\n *\n * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.\n * Oppositely, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}\n * once without retrying, and relies on the returned value to be true.\n *\n * Reverts if the returned value is other than `true`.\n */\n function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {\n if (to.code.length == 0) {\n forceApprove(token, to, value);\n } else if (!token.approveAndCall(to, value, data)) {\n revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Imitates a Solidity `token.transfer(to, value)` call, relaxing the requirement on the return value: the\n * return value is optional (but if data is returned, it must not be false).\n *\n * @param token The token targeted by the call.\n * @param to The recipient of the tokens\n * @param value The amount of token to transfer\n * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.\n */\n function _safeTransfer(IERC20 token, address to, uint256 value, bool bubble) private returns (bool success) {\n bytes4 selector = IERC20.transfer.selector;\n\n assembly (\"memory-safe\") {\n let fmp := mload(0x40)\n mstore(0x00, selector)\n mstore(0x04, and(to, shr(96, not(0))))\n mstore(0x24, value)\n success := call(gas(), token, 0, 0x00, 0x44, 0x00, 0x20)\n // if call success and return is true, all is good.\n // otherwise (not success or return is not true), we need to perform further checks\n if iszero(and(success, eq(mload(0x00), 1))) {\n // if the call was a failure and bubble is enabled, bubble the error\n if and(iszero(success), bubble) {\n returndatacopy(fmp, 0x00, returndatasize())\n revert(fmp, returndatasize())\n }\n // if the return value is not true, then the call is only successful if:\n // - the token address has code\n // - the returndata is empty\n success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))\n }\n mstore(0x40, fmp)\n }\n }\n\n /**\n * @dev Imitates a Solidity `token.transferFrom(from, to, value)` call, relaxing the requirement on the return\n * value: the return value is optional (but if data is returned, it must not be false).\n *\n * @param token The token targeted by the call.\n * @param from The sender of the tokens\n * @param to The recipient of the tokens\n * @param value The amount of token to transfer\n * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.\n */\n function _safeTransferFrom(\n IERC20 token,\n address from,\n address to,\n uint256 value,\n bool bubble\n ) private returns (bool success) {\n bytes4 selector = IERC20.transferFrom.selector;\n\n assembly (\"memory-safe\") {\n let fmp := mload(0x40)\n mstore(0x00, selector)\n mstore(0x04, and(from, shr(96, not(0))))\n mstore(0x24, and(to, shr(96, not(0))))\n mstore(0x44, value)\n success := call(gas(), token, 0, 0x00, 0x64, 0x00, 0x20)\n // if call success and return is true, all is good.\n // otherwise (not success or return is not true), we need to perform further checks\n if iszero(and(success, eq(mload(0x00), 1))) {\n // if the call was a failure and bubble is enabled, bubble the error\n if and(iszero(success), bubble) {\n returndatacopy(fmp, 0x00, returndatasize())\n revert(fmp, returndatasize())\n }\n // if the return value is not true, then the call is only successful if:\n // - the token address has code\n // - the returndata is empty\n success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))\n }\n mstore(0x40, fmp)\n mstore(0x60, 0)\n }\n }\n\n /**\n * @dev Imitates a Solidity `token.approve(spender, value)` call, relaxing the requirement on the return value:\n * the return value is optional (but if data is returned, it must not be false).\n *\n * @param token The token targeted by the call.\n * @param spender The spender of the tokens\n * @param value The amount of token to transfer\n * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.\n */\n function _safeApprove(IERC20 token, address spender, uint256 value, bool bubble) private returns (bool success) {\n bytes4 selector = IERC20.approve.selector;\n\n assembly (\"memory-safe\") {\n let fmp := mload(0x40)\n mstore(0x00, selector)\n mstore(0x04, and(spender, shr(96, not(0))))\n mstore(0x24, value)\n success := call(gas(), token, 0, 0x00, 0x44, 0x00, 0x20)\n // if call success and return is true, all is good.\n // otherwise (not success or return is not true), we need to perform further checks\n if iszero(and(success, eq(mload(0x00), 1))) {\n // if the call was a failure and bubble is enabled, bubble the error\n if and(iszero(success), bubble) {\n returndatacopy(fmp, 0x00, returndatasize())\n revert(fmp, returndatasize())\n }\n // if the return value is not true, then the call is only successful if:\n // - the token address has code\n // - the returndata is empty\n success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))\n }\n mstore(0x40, fmp)\n }\n }\n}\n" + }, + "@openzeppelin/contracts/utils/Context.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n function _contextSuffixLength() internal view virtual returns (uint256) {\n return 0;\n }\n}\n" + }, + "@openzeppelin/contracts/utils/introspection/IERC165.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)\n\npragma solidity >=0.4.16;\n\n/**\n * @dev Interface of the ERC-165 standard, as defined in the\n * https://eips.ethereum.org/EIPS/eip-165[ERC].\n *\n * Implementers can declare support of contract interfaces, which can then be\n * queried by others ({ERC165Checker}).\n *\n * For an implementation, see {ERC165}.\n */\ninterface IERC165 {\n /**\n * @dev Returns true if this contract implements the interface defined by\n * `interfaceId`. See the corresponding\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]\n * to learn more about how these ids are created.\n *\n * This function call must use less than 30 000 gas.\n */\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n" + }, + "contracts/mocks/MyOFTMock.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\r\npragma solidity ^0.8.22;\r\n\r\nimport { MyOFT } from \"../MyOFT.sol\";\r\n\r\n// @dev WARNING: This is for testing purposes only\r\ncontract MyOFTMock is MyOFT {\r\n constructor(\r\n string memory _name,\r\n string memory _symbol,\r\n address _lzEndpoint,\r\n address _delegate\r\n ) MyOFT(_name, _symbol, _lzEndpoint, _delegate) {}\r\n\r\n // Now identical to the inherited MyOFT.mint, kept as an explicit override so the mock's\r\n // intent stays visible at the point tests read it.\r\n function mint(address _to, uint256 _amount) public override {\r\n _mint(_to, _amount);\r\n }\r\n}\r\n" + }, + "contracts/MyOFT.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\r\npragma solidity ^0.8.22;\r\n\r\nimport { Ownable } from \"@openzeppelin/contracts/access/Ownable.sol\";\r\nimport { OFT } from \"@layerzerolabs/oft-evm/contracts/OFT.sol\";\r\n\r\ncontract MyOFT is OFT {\r\n constructor(\r\n string memory _name,\r\n string memory _symbol,\r\n address _lzEndpoint,\r\n address _delegate\r\n ) OFT(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) {}\r\n\r\n /// @notice Open mint, TESTNET ONLY — same as ToyOFT, so the demo tasks work against either.\r\n /// @dev Deliberately unguarded: the demo mints to itself before each send, and a testnet OFT\r\n /// with no way to obtain tokens cannot be used to exercise the DVN at all. Do NOT ship\r\n /// this to a network where the token has value.\r\n /// `virtual` because MyOFTMock declares the same function for the hardhat tests.\r\n function mint(address _to, uint256 _amount) public virtual {\r\n _mint(_to, _amount);\r\n }\r\n}\r\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "evmVersion": "paris", + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + "devdoc", + "userdoc", + "storageLayout", + "evm.gasEstimates" + ], + "": [ + "ast" + ] + } + }, + "metadata": { + "useLiteralContent": true + } + } +} \ No newline at end of file diff --git a/deployments/base-sepolia/solcInputs/91f67dad93f3438967ecce8dd1aaa287.json b/deployments/base-sepolia/solcInputs/91f67dad93f3438967ecce8dd1aaa287.json new file mode 100644 index 0000000..6554eb6 --- /dev/null +++ b/deployments/base-sepolia/solcInputs/91f67dad93f3438967ecce8dd1aaa287.json @@ -0,0 +1,36 @@ +{ + "language": "Solidity", + "sources": { + "contracts/mocks/RiskyProxyMock.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.22;\n\n/// @title RiskyProxyMock\n/// @notice A testnet decoy that looks like an upgradeable proxy controlled by a flagged address,\n/// for exercising the risk engine's `contract_admin_risk` check.\n/// @dev The engine reads the two EIP-1967 slots directly (see `assess/providers/contract.ts`):\n/// an implementation slot that is set means the code behind this address can change, and the\n/// admin slot names whoever can change it. It then looks that admin up in the risk store —\n/// a flagged admin is the signal, because today's clean code says nothing about tomorrow's\n/// if a sanctioned party can swap it out.\n///\n/// The slots are written straight to storage rather than by deploying a real proxy: what is\n/// being demonstrated is the engine's reading of them, and a forwarding proxy would add a\n/// delegatecall path with nothing to delegate to.\ncontract RiskyProxyMock {\n /// @dev keccak256(\"eip1967.proxy.implementation\") - 1\n bytes32 private constant SLOT_IMPLEMENTATION =\n 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\n /// @dev keccak256(\"eip1967.proxy.admin\") - 1\n bytes32 private constant SLOT_ADMIN = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;\n\n /// @param _admin The address to present as able to upgrade this contract. Point it at an\n /// address the operator has flagged (e.g. one in TEST_DENYLIST) for the check to fire.\n /// @param _implementation Any non-zero address; its only job is to make the proxy slot set.\n constructor(address _admin, address _implementation) {\n require(_admin != address(0), \"zero admin\");\n require(_implementation != address(0), \"zero implementation\");\n assembly {\n sstore(SLOT_ADMIN, _admin)\n sstore(SLOT_IMPLEMENTATION, _implementation)\n }\n }\n\n /// @notice The admin as stored in the EIP-1967 slot, for anyone reading it the easy way.\n function admin() external view returns (address a) {\n assembly {\n a := sload(SLOT_ADMIN)\n }\n }\n\n /// @notice The implementation as stored in the EIP-1967 slot.\n function implementation() external view returns (address i) {\n assembly {\n i := sload(SLOT_IMPLEMENTATION)\n }\n }\n}\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "evmVersion": "paris", + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + "devdoc", + "userdoc", + "storageLayout", + "evm.gasEstimates" + ], + "": [ + "ast" + ] + } + }, + "metadata": { + "useLiteralContent": true + } + } +} \ No newline at end of file diff --git a/deployments/base-sepolia/solcInputs/97ae1cbcc67ee4ffae50031ebd6ec920.json b/deployments/base-sepolia/solcInputs/97ae1cbcc67ee4ffae50031ebd6ec920.json new file mode 100644 index 0000000..013214b --- /dev/null +++ b/deployments/base-sepolia/solcInputs/97ae1cbcc67ee4ffae50031ebd6ec920.json @@ -0,0 +1,48 @@ +{ + "language": "Solidity", + "sources": { + "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/ILayerZeroDVN.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\ninterface ILayerZeroDVN {\n struct AssignJobParam {\n uint32 dstEid;\n bytes packetHeader;\n bytes32 payloadHash;\n uint64 confirmations;\n address sender;\n }\n\n // @notice query price and assign jobs at the same time\n // @param _dstEid - the destination endpoint identifier\n // @param _packetHeader - version + nonce + path\n // @param _payloadHash - hash of guid + message\n // @param _confirmations - block confirmation delay before relaying blocks\n // @param _sender - the source sending contract address\n // @param _options - options\n function assignJob(AssignJobParam calldata _param, bytes calldata _options) external payable returns (uint256 fee);\n\n // @notice query the dvn fee for relaying block information to the destination chain\n // @param _dstEid the destination endpoint identifier\n // @param _confirmations - block confirmation delay before relaying blocks\n // @param _sender - the source sending contract address\n // @param _options - options\n function getFee(\n uint32 _dstEid,\n uint64 _confirmations,\n address _sender,\n bytes calldata _options\n ) external view returns (uint256 fee);\n}\n" + }, + "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/IReceiveUlnE2.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\n/// @dev should be implemented by the ReceiveUln302 contract and future ReceiveUln contracts on EndpointV2\ninterface IReceiveUlnE2 {\n /// @notice for each dvn to verify the payload\n /// @dev this function signature 0x0223536e\n function verify(bytes calldata _packetHeader, bytes32 _payloadHash, uint64 _confirmations) external;\n\n /// @notice verify the payload at endpoint, will check if all DVNs verified\n function commitVerification(bytes calldata _packetHeader, bytes32 _payloadHash) external;\n}\n" + }, + "@openzeppelin/contracts/access/Ownable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\n\npragma solidity ^0.8.20;\n\nimport {Context} from \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * The initial owner is set to the address provided by the deployer. This can\n * later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n /**\n * @dev The caller account is not authorized to perform an operation.\n */\n error OwnableUnauthorizedAccount(address account);\n\n /**\n * @dev The owner is not a valid owner account. (eg. `address(0)`)\n */\n error OwnableInvalidOwner(address owner);\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\n */\n constructor(address initialOwner) {\n if (initialOwner == address(0)) {\n revert OwnableInvalidOwner(address(0));\n }\n _transferOwnership(initialOwner);\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n if (owner() != _msgSender()) {\n revert OwnableUnauthorizedAccount(_msgSender());\n }\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby disabling any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n if (newOwner == address(0)) {\n revert OwnableInvalidOwner(address(0));\n }\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n" + }, + "@openzeppelin/contracts/utils/Context.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n function _contextSuffixLength() internal view virtual returns (uint256) {\n return 0;\n }\n}\n" + }, + "contracts/ComplianceDVN.sol": { + "content": "// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.22;\r\n\r\nimport { Ownable } from \"@openzeppelin/contracts/access/Ownable.sol\";\r\nimport { ILayerZeroDVN } from \"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/ILayerZeroDVN.sol\";\r\nimport { IReceiveUlnE2 } from \"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/IReceiveUlnE2.sol\";\r\n\r\n/// @title ComplianceDVN\r\n/// @notice Thin LayerZero V2 DVN. All compliance judgment is off-chain; the on-chain\r\n/// contract only conforms to the worker-job interface and gates the destination\r\n/// attestation behind an operator key. Withholding `submitVerification` IS the veto.\r\ncontract ComplianceDVN is ILayerZeroDVN, Ownable {\r\n address public operator; // off-chain worker key\r\n address public sendUln; // SendUln302 on this chain — the only address allowed to assign jobs\r\n address public receiveUln; // ReceiveUln302 on this chain\r\n uint256 public fee;\r\n\r\n event JobAssigned(uint32 dstEid, bytes32 payloadHash, uint64 confirmations, address sender);\r\n event OperatorSet(address operator);\r\n event SendUlnSet(address sendUln);\r\n event ReceiveUlnSet(address receiveUln);\r\n event FeeSet(uint256 fee);\r\n\r\n /// @notice A held packet cleared for verification by the owner. Deliberately owner-only:\r\n /// the worker holds only the operator key, so it cannot approve its own holds.\r\n event PacketApproved(bytes32 indexed payloadHash, address approver);\r\n\r\n /// @notice The risk decision behind a packet's outcome.\r\n /// @param payloadHash the packet this verdict is about\r\n /// @param action ACTION_* below\r\n /// @param score 0-100 risk score the action was derived from\r\n /// @param reasonMask bitmask of reason codes; bit assignments are append-only and\r\n /// documented in the worker's `assess/verdict.ts`\r\n /// @param evidenceHash keccak256 of the canonical evidence document held off-chain\r\n event RiskVerdict(\r\n bytes32 indexed payloadHash,\r\n uint8 action,\r\n uint16 score,\r\n uint256 reasonMask,\r\n bytes32 evidenceHash\r\n );\r\n\r\n /// @dev Action codes. These are part of the event ABI: an indexer decoding old logs relies\r\n /// on them, so the numbering is permanent. Kept in sync with the worker's ACTION_CODES.\r\n uint8 public constant ACTION_ALLOW = 0;\r\n uint8 public constant ACTION_DELAY = 1;\r\n uint8 public constant ACTION_MANUAL_REVIEW = 2;\r\n uint8 public constant ACTION_BLOCK = 3;\r\n\r\n error NotOperator();\r\n error NotSendLibrary();\r\n error UnknownAction(uint8 action);\r\n /// @dev Submitting a verification asserts the packet was allowed; any other action would be\r\n /// a self-contradicting record.\r\n error VerificationRequiresAllow(uint8 action);\r\n /// @dev An allow rides along on `submitVerification`, so recording one separately would\r\n /// double-report the same outcome.\r\n error AllowNotSeparatelyRecorded();\r\n\r\n modifier onlyOperator() {\r\n if (msg.sender != operator) revert NotOperator();\r\n _;\r\n }\r\n\r\n constructor(\r\n address _owner,\r\n address _operator,\r\n address _sendUln,\r\n address _receiveUln,\r\n uint256 _fee\r\n ) Ownable(_owner) {\r\n require(_operator != address(0), \"zero operator\");\r\n require(_sendUln != address(0), \"zero sendUln\");\r\n require(_receiveUln != address(0), \"zero receiveUln\");\r\n operator = _operator;\r\n sendUln = _sendUln;\r\n receiveUln = _receiveUln;\r\n fee = _fee;\r\n }\r\n\r\n function getFee(\r\n uint32 /*_dstEid*/,\r\n uint64 /*_confirmations*/,\r\n address /*_sender*/,\r\n bytes calldata /*_options*/\r\n ) external view returns (uint256) {\r\n return fee;\r\n }\r\n\r\n function assignJob(AssignJobParam calldata _param, bytes calldata) external payable returns (uint256) {\r\n // Only the send library assigns jobs. The worker treats a JobAssigned payloadHash as\r\n // \"this packet is ours to screen\" and spends operator gas verifying it, so an open\r\n // assignJob would let anyone point the worker at packets no one asked it to verify.\r\n if (msg.sender != sendUln) revert NotSendLibrary();\r\n // NOTE: SendUln302 calls assignJob WITHOUT forwarding value (msg.value == 0); the\r\n // messagelib accrues each worker's fee internally and workers withdraw separately\r\n // (see SendUlnBase._assignJobs). So we must NOT require msg.value >= fee here — doing\r\n // so reverts every real send. We simply record the job and return our fee quote.\r\n emit JobAssigned(_param.dstEid, _param.payloadHash, _param.confirmations, _param.sender);\r\n return fee;\r\n }\r\n\r\n /// @notice Attest a packet and record the risk verdict that permitted it, in one call.\r\n /// @dev The verdict rides along at no extra transaction cost, so an allowed packet always\r\n /// carries an auditable reason for having been allowed. `action` must be ACTION_ALLOW:\r\n /// a packet that was blocked or held cannot also have been verified. An owner-approved\r\n /// release is reported as ACTION_ALLOW too — a human allowed it — with the reason mask\r\n /// still carrying why it had been held.\r\n function submitVerification(\r\n bytes calldata packetHeader,\r\n bytes32 payloadHash,\r\n uint64 confirmations,\r\n uint8 action,\r\n uint16 score,\r\n uint256 reasonMask,\r\n bytes32 evidenceHash\r\n ) external onlyOperator {\r\n if (action != ACTION_ALLOW) revert VerificationRequiresAllow(action);\r\n IReceiveUlnE2(receiveUln).verify(packetHeader, payloadHash, confirmations);\r\n emit RiskVerdict(payloadHash, action, score, reasonMask, evidenceHash);\r\n }\r\n\r\n /// @notice Record a verdict for a packet that was NOT verified.\r\n /// @dev Withholding the attestation is what actually stops the packet; this only leaves the\r\n /// audit trail. It is therefore best-effort by design — the worker treats a failure\r\n /// here as a lost record, never as a failure to enforce.\r\n function recordVerdict(\r\n bytes32 payloadHash,\r\n uint8 action,\r\n uint16 score,\r\n uint256 reasonMask,\r\n bytes32 evidenceHash\r\n ) external onlyOperator {\r\n if (action > ACTION_BLOCK) revert UnknownAction(action);\r\n if (action == ACTION_ALLOW) revert AllowNotSeparatelyRecorded();\r\n emit RiskVerdict(payloadHash, action, score, reasonMask, evidenceHash);\r\n }\r\n\r\n /// @notice Clear a packet the worker withheld for manual review.\r\n /// @dev Emits only; no storage. The worker observes `PacketApproved` and releases the\r\n /// packet from its local deferred queue. Approval is a human override of a risk\r\n /// verdict, so it is separated from the operator key by design — a compromised or\r\n /// buggy worker cannot approve the packets it chose to hold.\r\n function approvePacket(bytes32 payloadHash) external onlyOwner {\r\n emit PacketApproved(payloadHash, msg.sender);\r\n }\r\n\r\n function setOperator(address _operator) external onlyOwner {\r\n require(_operator != address(0), \"zero operator\");\r\n operator = _operator;\r\n emit OperatorSet(_operator);\r\n }\r\n\r\n function setSendUln(address _sendUln) external onlyOwner {\r\n require(_sendUln != address(0), \"zero sendUln\");\r\n sendUln = _sendUln;\r\n emit SendUlnSet(_sendUln);\r\n }\r\n\r\n function setReceiveUln(address _receiveUln) external onlyOwner {\r\n require(_receiveUln != address(0), \"zero receiveUln\");\r\n receiveUln = _receiveUln;\r\n emit ReceiveUlnSet(_receiveUln);\r\n }\r\n\r\n function setFee(uint256 _fee) external onlyOwner {\r\n fee = _fee;\r\n emit FeeSet(_fee);\r\n }\r\n\r\n function withdraw(address payable _to) external onlyOwner {\r\n (bool ok, ) = _to.call{ value: address(this).balance }(\"\");\r\n require(ok, \"withdraw failed\");\r\n }\r\n}\r\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "evmVersion": "paris", + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + "devdoc", + "userdoc", + "storageLayout", + "evm.gasEstimates" + ], + "": [ + "ast" + ] + } + }, + "metadata": { + "useLiteralContent": true + } + } +} \ No newline at end of file diff --git a/deployments/optimism-sepolia/ComplianceDVN.json b/deployments/optimism-sepolia/ComplianceDVN.json index dd23c58..4e89908 100644 --- a/deployments/optimism-sepolia/ComplianceDVN.json +++ b/deployments/optimism-sepolia/ComplianceDVN.json @@ -1,5 +1,5 @@ { - "address": "0x8bc1f192391018Ee605D7A8D9B761159d91092C3", + "address": "0x7843CAf643A175fc3d0E4746678BEdAAFc396e65", "abi": [ { "inputs": [ @@ -13,6 +13,11 @@ "name": "_operator", "type": "address" }, + { + "internalType": "address", + "name": "_sendUln", + "type": "address" + }, { "internalType": "address", "name": "_receiveUln", @@ -27,11 +32,21 @@ "stateMutability": "nonpayable", "type": "constructor" }, + { + "inputs": [], + "name": "AllowNotSeparatelyRecorded", + "type": "error" + }, { "inputs": [], "name": "NotOperator", "type": "error" }, + { + "inputs": [], + "name": "NotSendLibrary", + "type": "error" + }, { "inputs": [ { @@ -54,6 +69,28 @@ "name": "OwnableUnauthorizedAccount", "type": "error" }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "action", + "type": "uint8" + } + ], + "name": "UnknownAction", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "action", + "type": "uint8" + } + ], + "name": "VerificationRequiresAllow", + "type": "error" + }, { "anonymous": false, "inputs": [ @@ -130,6 +167,25 @@ "name": "OwnershipTransferred", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "payloadHash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "PacketApproved", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -143,6 +199,121 @@ "name": "ReceiveUlnSet", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "payloadHash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "action", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "score", + "type": "uint16" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reasonMask", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "evidenceHash", + "type": "bytes32" + } + ], + "name": "RiskVerdict", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "sendUln", + "type": "address" + } + ], + "name": "SendUlnSet", + "type": "event" + }, + { + "inputs": [], + "name": "ACTION_ALLOW", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ACTION_BLOCK", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ACTION_DELAY", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ACTION_MANUAL_REVIEW", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "payloadHash", + "type": "bytes32" + } + ], + "name": "approvePacket", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -280,6 +451,39 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "payloadHash", + "type": "bytes32" + }, + { + "internalType": "uint8", + "name": "action", + "type": "uint8" + }, + { + "internalType": "uint16", + "name": "score", + "type": "uint16" + }, + { + "internalType": "uint256", + "name": "reasonMask", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "evidenceHash", + "type": "bytes32" + } + ], + "name": "recordVerdict", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "renounceOwnership", @@ -287,6 +491,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "sendUln", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -326,6 +543,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "_sendUln", + "type": "address" + } + ], + "name": "setSendUln", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -342,6 +572,26 @@ "internalType": "uint64", "name": "confirmations", "type": "uint64" + }, + { + "internalType": "uint8", + "name": "action", + "type": "uint8" + }, + { + "internalType": "uint16", + "name": "score", + "type": "uint16" + }, + { + "internalType": "uint256", + "name": "reasonMask", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "evidenceHash", + "type": "bytes32" } ], "name": "submitVerification", @@ -376,50 +626,56 @@ "type": "function" } ], - "transactionHash": "0x5dcfde7d19562e65c132e3661fc379be0153bb793807bee91c922fb0e3538e75", + "transactionHash": "0x5b11587fd3661d88b9a517fb11caa0246ab21df94ca2f90bce3043a2d063fb11", "receipt": { "to": null, - "from": "0x69BD4d7ec258E29d6A9ADD925a543706DBde210c", - "contractAddress": "0x8bc1f192391018Ee605D7A8D9B761159d91092C3", + "from": "0x8583894d0e57e42abb83039537f314490038efa0", + "contractAddress": "0x7843CAf643A175fc3d0E4746678BEdAAFc396e65", "transactionIndex": 2, - "gasUsed": "673354", - "logsBloom": "0x00000000000800000000000000000000000000000000000000800000000000000000000000000000000000200000000000000000000000000000000000000000000020000000000000000000020000000001000008000000000000000000000000000000020000000000000000000800000000000000000000000000000000400000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000", - "blockHash": "0x42579462921fdc642d2d28e89751de127561d5b18a645a28e4240403b5231d3b", - "transactionHash": "0x5dcfde7d19562e65c132e3661fc379be0153bb793807bee91c922fb0e3538e75", + "gasUsed": "933268", + "logsBloom": "0x00000000000000000000000000000000000000000000002000800000000000000000000000000000020000000000000000000002000000000000000000000000000000000000000000000000000000000001000000000000000000000000002000000100020000000000000000000800000000000000000000000000000000400000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x7a6584af57071ff68c1c5a48b97d1c85bb1dc3b93be775dd928e5ba480072ca7", + "transactionHash": "0x5b11587fd3661d88b9a517fb11caa0246ab21df94ca2f90bce3043a2d063fb11", "logs": [ { "transactionIndex": 2, - "blockNumber": 44767041, - "transactionHash": "0x5dcfde7d19562e65c132e3661fc379be0153bb793807bee91c922fb0e3538e75", - "address": "0x8bc1f192391018Ee605D7A8D9B761159d91092C3", + "blockNumber": 46858398, + "transactionHash": "0x5b11587fd3661d88b9a517fb11caa0246ab21df94ca2f90bce3043a2d063fb11", + "address": "0x7843CAf643A175fc3d0E4746678BEdAAFc396e65", "topics": [ "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x00000000000000000000000069bd4d7ec258e29d6a9add925a543706dbde210c" + "0x0000000000000000000000008583894d0e57e42abb83039537f314490038efa0" ], "data": "0x", - "logIndex": 3, - "blockHash": "0x42579462921fdc642d2d28e89751de127561d5b18a645a28e4240403b5231d3b" + "logIndex": 2, + "blockHash": "0x7a6584af57071ff68c1c5a48b97d1c85bb1dc3b93be775dd928e5ba480072ca7" } ], - "blockNumber": 44767041, - "cumulativeGasUsed": "905492", + "blockNumber": 46858398, + "cumulativeGasUsed": "1097738", "status": 1, "byzantium": true }, "args": [ - "0x69BD4d7ec258E29d6A9ADD925a543706DBde210c", - "0x69BD4d7ec258E29d6A9ADD925a543706DBde210c", + "0x8583894d0e57e42abb83039537f314490038efa0", + "0x01D24AE2cD8ad18472BD00AfE4ec425E800e184d", + "0xB31D2cb502E25B30C651842C7C3293c51Fe6d16f", "0x9284fd59B95b9143AF0b9795CAC16eb3C723C9Ca", "50000000000000" ], - "numDeployments": 1, - "solcInputHash": "072b17b3dad771bc29c928d7d4532ec5", - "metadata": "{\"compiler\":{\"version\":\"0.8.22+commit.4fc1097e\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_operator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_receiveUln\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_fee\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"NotOperator\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"OwnableInvalidOwner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"OwnableUnauthorizedAccount\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"fee\",\"type\":\"uint256\"}],\"name\":\"FeeSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"payloadHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"confirmations\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"JobAssigned\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"}],\"name\":\"OperatorSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"receiveUln\",\"type\":\"address\"}],\"name\":\"ReceiveUlnSet\",\"type\":\"event\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"packetHeader\",\"type\":\"bytes\"},{\"internalType\":\"bytes32\",\"name\":\"payloadHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"confirmations\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"internalType\":\"struct ILayerZeroDVN.AssignJobParam\",\"name\":\"_param\",\"type\":\"tuple\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"name\":\"assignJob\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"fee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"},{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"name\":\"getFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"operator\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"receiveUln\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_fee\",\"type\":\"uint256\"}],\"name\":\"setFee\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_operator\",\"type\":\"address\"}],\"name\":\"setOperator\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_receiveUln\",\"type\":\"address\"}],\"name\":\"setReceiveUln\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"packetHeader\",\"type\":\"bytes\"},{\"internalType\":\"bytes32\",\"name\":\"payloadHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"confirmations\",\"type\":\"uint64\"}],\"name\":\"submitVerification\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address payable\",\"name\":\"_to\",\"type\":\"address\"}],\"name\":\"withdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"errors\":{\"OwnableInvalidOwner(address)\":[{\"details\":\"The owner is not a valid owner account. (eg. `address(0)`)\"}],\"OwnableUnauthorizedAccount(address)\":[{\"details\":\"The caller account is not authorized to perform an operation.\"}]},\"kind\":\"dev\",\"methods\":{\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner.\"},\"transferOwnership(address)\":{\"details\":\"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.\"}},\"title\":\"ComplianceDVN\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"notice\":\"Thin LayerZero V2 DVN. All compliance judgment is off-chain; the on-chain contract only conforms to the worker-job interface and gates the destination attestation behind an operator key. Withholding `submitVerification` IS the veto.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/ComplianceDVN.sol\":\"ComplianceDVN\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/ILayerZeroDVN.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\ninterface ILayerZeroDVN {\\n struct AssignJobParam {\\n uint32 dstEid;\\n bytes packetHeader;\\n bytes32 payloadHash;\\n uint64 confirmations;\\n address sender;\\n }\\n\\n // @notice query price and assign jobs at the same time\\n // @param _dstEid - the destination endpoint identifier\\n // @param _packetHeader - version + nonce + path\\n // @param _payloadHash - hash of guid + message\\n // @param _confirmations - block confirmation delay before relaying blocks\\n // @param _sender - the source sending contract address\\n // @param _options - options\\n function assignJob(AssignJobParam calldata _param, bytes calldata _options) external payable returns (uint256 fee);\\n\\n // @notice query the dvn fee for relaying block information to the destination chain\\n // @param _dstEid the destination endpoint identifier\\n // @param _confirmations - block confirmation delay before relaying blocks\\n // @param _sender - the source sending contract address\\n // @param _options - options\\n function getFee(\\n uint32 _dstEid,\\n uint64 _confirmations,\\n address _sender,\\n bytes calldata _options\\n ) external view returns (uint256 fee);\\n}\\n\",\"keccak256\":\"0x308e77078242fd5c5746ec29c12e618249134f9e4377c0028ab8f59c07a6014b\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/IReceiveUlnE2.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\n/// @dev should be implemented by the ReceiveUln302 contract and future ReceiveUln contracts on EndpointV2\\ninterface IReceiveUlnE2 {\\n /// @notice for each dvn to verify the payload\\n /// @dev this function signature 0x0223536e\\n function verify(bytes calldata _packetHeader, bytes32 _payloadHash, uint64 _confirmations) external;\\n\\n /// @notice verify the payload at endpoint, will check if all DVNs verified\\n function commitVerification(bytes calldata _packetHeader, bytes32 _payloadHash) external;\\n}\\n\",\"keccak256\":\"0xcdf7e690e5d5c0a3ec26a0d7b1a7fe49c7d16a3634721c3944f77d13ff5d4a91\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/Ownable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\\n\\npragma solidity ^0.8.20;\\n\\nimport {Context} from \\\"../utils/Context.sol\\\";\\n\\n/**\\n * @dev Contract module which provides a basic access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * The initial owner is set to the address provided by the deployer. This can\\n * later be changed with {transferOwnership}.\\n *\\n * This module is used through inheritance. It will make available the modifier\\n * `onlyOwner`, which can be applied to your functions to restrict their use to\\n * the owner.\\n */\\nabstract contract Ownable is Context {\\n address private _owner;\\n\\n /**\\n * @dev The caller account is not authorized to perform an operation.\\n */\\n error OwnableUnauthorizedAccount(address account);\\n\\n /**\\n * @dev The owner is not a valid owner account. (eg. `address(0)`)\\n */\\n error OwnableInvalidOwner(address owner);\\n\\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\\n\\n /**\\n * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\\n */\\n constructor(address initialOwner) {\\n if (initialOwner == address(0)) {\\n revert OwnableInvalidOwner(address(0));\\n }\\n _transferOwnership(initialOwner);\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the owner.\\n */\\n modifier onlyOwner() {\\n _checkOwner();\\n _;\\n }\\n\\n /**\\n * @dev Returns the address of the current owner.\\n */\\n function owner() public view virtual returns (address) {\\n return _owner;\\n }\\n\\n /**\\n * @dev Throws if the sender is not the owner.\\n */\\n function _checkOwner() internal view virtual {\\n if (owner() != _msgSender()) {\\n revert OwnableUnauthorizedAccount(_msgSender());\\n }\\n }\\n\\n /**\\n * @dev Leaves the contract without owner. It will not be possible to call\\n * `onlyOwner` functions. Can only be called by the current owner.\\n *\\n * NOTE: Renouncing ownership will leave the contract without an owner,\\n * thereby disabling any functionality that is only available to the owner.\\n */\\n function renounceOwnership() public virtual onlyOwner {\\n _transferOwnership(address(0));\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual onlyOwner {\\n if (newOwner == address(0)) {\\n revert OwnableInvalidOwner(address(0));\\n }\\n _transferOwnership(newOwner);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Internal function without access restriction.\\n */\\n function _transferOwnership(address newOwner) internal virtual {\\n address oldOwner = _owner;\\n _owner = newOwner;\\n emit OwnershipTransferred(oldOwner, newOwner);\\n }\\n}\\n\",\"keccak256\":\"0xff6d0bb2e285473e5311d9d3caacb525ae3538a80758c10649a4d61029b017bb\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Context.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\\n\\npragma solidity ^0.8.20;\\n\\n/**\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract Context {\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n\\n function _contextSuffixLength() internal view virtual returns (uint256) {\\n return 0;\\n }\\n}\\n\",\"keccak256\":\"0x493033a8d1b176a037b2cc6a04dad01a5c157722049bbecf632ca876224dd4b2\",\"license\":\"MIT\"},\"contracts/ComplianceDVN.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.22;\\n\\nimport { Ownable } from \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\nimport { ILayerZeroDVN } from \\\"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/ILayerZeroDVN.sol\\\";\\nimport { IReceiveUlnE2 } from \\\"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/IReceiveUlnE2.sol\\\";\\n\\n/// @title ComplianceDVN\\n/// @notice Thin LayerZero V2 DVN. All compliance judgment is off-chain; the on-chain\\n/// contract only conforms to the worker-job interface and gates the destination\\n/// attestation behind an operator key. Withholding `submitVerification` IS the veto.\\ncontract ComplianceDVN is ILayerZeroDVN, Ownable {\\n address public operator; // off-chain worker key\\n address public receiveUln; // ReceiveUln302 on this chain\\n uint256 public fee;\\n\\n event JobAssigned(uint32 dstEid, bytes32 payloadHash, uint64 confirmations, address sender);\\n event OperatorSet(address operator);\\n event ReceiveUlnSet(address receiveUln);\\n event FeeSet(uint256 fee);\\n\\n error NotOperator();\\n\\n modifier onlyOperator() {\\n if (msg.sender != operator) revert NotOperator();\\n _;\\n }\\n\\n constructor(address _owner, address _operator, address _receiveUln, uint256 _fee) Ownable(_owner) {\\n require(_operator != address(0), \\\"zero operator\\\");\\n require(_receiveUln != address(0), \\\"zero receiveUln\\\");\\n operator = _operator;\\n receiveUln = _receiveUln;\\n fee = _fee;\\n }\\n\\n function getFee(\\n uint32 /*_dstEid*/,\\n uint64 /*_confirmations*/,\\n address /*_sender*/,\\n bytes calldata /*_options*/\\n ) external view returns (uint256) {\\n return fee;\\n }\\n\\n function assignJob(AssignJobParam calldata _param, bytes calldata) external payable returns (uint256) {\\n // NOTE: SendUln302 calls assignJob WITHOUT forwarding value (msg.value == 0); the\\n // messagelib accrues each worker's fee internally and workers withdraw separately\\n // (see SendUlnBase._assignJobs). So we must NOT require msg.value >= fee here \\u2014 doing\\n // so reverts every real send. We simply record the job and return our fee quote.\\n emit JobAssigned(_param.dstEid, _param.payloadHash, _param.confirmations, _param.sender);\\n return fee;\\n }\\n\\n function submitVerification(\\n bytes calldata packetHeader,\\n bytes32 payloadHash,\\n uint64 confirmations\\n ) external onlyOperator {\\n IReceiveUlnE2(receiveUln).verify(packetHeader, payloadHash, confirmations);\\n }\\n\\n function setOperator(address _operator) external onlyOwner {\\n require(_operator != address(0), \\\"zero operator\\\");\\n operator = _operator;\\n emit OperatorSet(_operator);\\n }\\n\\n function setReceiveUln(address _receiveUln) external onlyOwner {\\n require(_receiveUln != address(0), \\\"zero receiveUln\\\");\\n receiveUln = _receiveUln;\\n emit ReceiveUlnSet(_receiveUln);\\n }\\n\\n function setFee(uint256 _fee) external onlyOwner {\\n fee = _fee;\\n emit FeeSet(_fee);\\n }\\n\\n function withdraw(address payable _to) external onlyOwner {\\n (bool ok, ) = _to.call{ value: address(this).balance }(\\\"\\\");\\n require(ok, \\\"withdraw failed\\\");\\n }\\n}\\n\",\"keccak256\":\"0x335c7811fc779c745ce8b7c8e268d3b4ff6f7625ee0ef4aa8ca8040f8c40314b\",\"license\":\"MIT\"}},\"version\":1}", - "bytecode": "0x608060405234801561001057600080fd5b50604051610b61380380610b6183398101604081905261002f9161019c565b836001600160a01b03811661005f57604051631e4fbdf760e01b8152600060048201526024015b60405180910390fd5b61006881610130565b506001600160a01b0383166100af5760405162461bcd60e51b815260206004820152600d60248201526c3d32b9379037b832b930ba37b960991b6044820152606401610056565b6001600160a01b0382166100f75760405162461bcd60e51b815260206004820152600f60248201526e3d32b937903932b1b2b4bb32aab63760891b6044820152606401610056565b600180546001600160a01b039485166001600160a01b0319918216179091556002805493909416921691909117909155600355506101e7565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b80516001600160a01b038116811461019757600080fd5b919050565b600080600080608085870312156101b257600080fd5b6101bb85610180565b93506101c960208601610180565b92506101d760408601610180565b6060959095015193969295505050565b61096b806101f66000396000f3fe6080604052600436106100c25760003560e01c80638da5cb5b1161007f578063ddca3f4311610059578063ddca3f4314610201578063e3b6f56714610217578063e49ca47114610237578063f2fde38b1461025757600080fd5b80638da5cb5b146101b057806395d376d7146101ce578063b3ab15fb146101e157600080fd5b806330bb3aac146100c757806348c8e2d61461010157806351cff8d914610123578063570ca7351461014357806369fe0e2d1461017b578063715018a61461019b575b600080fd5b3480156100d357600080fd5b506100ee6100e2366004610732565b60035495945050505050565b6040519081526020015b60405180910390f35b34801561010d57600080fd5b5061012161011c3660046107aa565b610277565b005b34801561012f57600080fd5b5061012161013e366004610807565b610310565b34801561014f57600080fd5b50600154610163906001600160a01b031681565b6040516001600160a01b0390911681526020016100f8565b34801561018757600080fd5b5061012161019636600461082b565b6103b6565b3480156101a757600080fd5b506101216103fa565b3480156101bc57600080fd5b506000546001600160a01b0316610163565b6100ee6101dc366004610844565b61040e565b3480156101ed57600080fd5b506101216101fc366004610807565b6104ae565b34801561020d57600080fd5b506100ee60035481565b34801561022357600080fd5b50610121610232366004610807565b61054a565b34801561024357600080fd5b50600254610163906001600160a01b031681565b34801561026357600080fd5b50610121610272366004610807565b6105e8565b6001546001600160a01b031633146102a257604051631f0853c160e21b815260040160405180910390fd5b600254604051630111a9b760e11b81526001600160a01b0390911690630223536e906102d89087908790879087906004016108b5565b600060405180830381600087803b1580156102f257600080fd5b505af1158015610306573d6000803e3d6000fd5b5050505050505050565b610318610626565b6000816001600160a01b03164760405160006040518083038185875af1925050503d8060008114610365576040519150601f19603f3d011682016040523d82523d6000602084013e61036a565b606091505b50509050806103b25760405162461bcd60e51b815260206004820152600f60248201526e1dda5d1a191c985dc819985a5b1959608a1b60448201526064015b60405180910390fd5b5050565b6103be610626565b60038190556040518181527f20461e09b8e557b77e107939f9ce6544698123aad0fc964ac5cc59b7df2e608f906020015b60405180910390a150565b610402610626565b61040c6000610653565b565b60007ff266d2fc560c9aaf31e6bdf020cac0f2a9d5b3aa16f932e16434749ddff1499961043e60208601866108ff565b6040860135610453608088016060890161091a565b61046360a0890160808a01610807565b6040805163ffffffff959095168552602085019390935267ffffffffffffffff91909116838301526001600160a01b03166060830152519081900360800190a1506003549392505050565b6104b6610626565b6001600160a01b0381166104fc5760405162461bcd60e51b815260206004820152600d60248201526c3d32b9379037b832b930ba37b960991b60448201526064016103a9565b600180546001600160a01b0319166001600160a01b0383169081179091556040519081527f99d737e0adf2c449d71890b86772885ec7959b152ddb265f76325b6e68e105d3906020016103ef565b610552610626565b6001600160a01b03811661059a5760405162461bcd60e51b815260206004820152600f60248201526e3d32b937903932b1b2b4bb32aab63760891b60448201526064016103a9565b600280546001600160a01b0319166001600160a01b0383169081179091556040519081527f549e7b08606f710537aa4c9f00cb4d403d0048a8049411f3c1922d6e97744d6a906020016103ef565b6105f0610626565b6001600160a01b03811661061a57604051631e4fbdf760e01b8152600060048201526024016103a9565b61062381610653565b50565b6000546001600160a01b0316331461040c5760405163118cdaa760e01b81523360048201526024016103a9565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b803563ffffffff811681146106b757600080fd5b919050565b803567ffffffffffffffff811681146106b757600080fd5b6001600160a01b038116811461062357600080fd5b60008083601f8401126106fb57600080fd5b50813567ffffffffffffffff81111561071357600080fd5b60208301915083602082850101111561072b57600080fd5b9250929050565b60008060008060006080868803121561074a57600080fd5b610753866106a3565b9450610761602087016106bc565b93506040860135610771816106d4565b9250606086013567ffffffffffffffff81111561078d57600080fd5b610799888289016106e9565b969995985093965092949392505050565b600080600080606085870312156107c057600080fd5b843567ffffffffffffffff8111156107d757600080fd5b6107e3878288016106e9565b909550935050602085013591506107fc604086016106bc565b905092959194509250565b60006020828403121561081957600080fd5b8135610824816106d4565b9392505050565b60006020828403121561083d57600080fd5b5035919050565b60008060006040848603121561085957600080fd5b833567ffffffffffffffff8082111561087157600080fd5b9085019060a0828803121561088557600080fd5b9093506020850135908082111561089b57600080fd5b506108a8868287016106e9565b9497909650939450505050565b606081528360608201528385608083013760006080858301015260006080601f19601f870116830101905083602083015267ffffffffffffffff8316604083015295945050505050565b60006020828403121561091157600080fd5b610824826106a3565b60006020828403121561092c57600080fd5b610824826106bc56fea26469706673582212200429577ba4787f70d95d6de684082e4a59ee8692b228009a92be0dbe0d2d237164736f6c63430008160033", - "deployedBytecode": "0x6080604052600436106100c25760003560e01c80638da5cb5b1161007f578063ddca3f4311610059578063ddca3f4314610201578063e3b6f56714610217578063e49ca47114610237578063f2fde38b1461025757600080fd5b80638da5cb5b146101b057806395d376d7146101ce578063b3ab15fb146101e157600080fd5b806330bb3aac146100c757806348c8e2d61461010157806351cff8d914610123578063570ca7351461014357806369fe0e2d1461017b578063715018a61461019b575b600080fd5b3480156100d357600080fd5b506100ee6100e2366004610732565b60035495945050505050565b6040519081526020015b60405180910390f35b34801561010d57600080fd5b5061012161011c3660046107aa565b610277565b005b34801561012f57600080fd5b5061012161013e366004610807565b610310565b34801561014f57600080fd5b50600154610163906001600160a01b031681565b6040516001600160a01b0390911681526020016100f8565b34801561018757600080fd5b5061012161019636600461082b565b6103b6565b3480156101a757600080fd5b506101216103fa565b3480156101bc57600080fd5b506000546001600160a01b0316610163565b6100ee6101dc366004610844565b61040e565b3480156101ed57600080fd5b506101216101fc366004610807565b6104ae565b34801561020d57600080fd5b506100ee60035481565b34801561022357600080fd5b50610121610232366004610807565b61054a565b34801561024357600080fd5b50600254610163906001600160a01b031681565b34801561026357600080fd5b50610121610272366004610807565b6105e8565b6001546001600160a01b031633146102a257604051631f0853c160e21b815260040160405180910390fd5b600254604051630111a9b760e11b81526001600160a01b0390911690630223536e906102d89087908790879087906004016108b5565b600060405180830381600087803b1580156102f257600080fd5b505af1158015610306573d6000803e3d6000fd5b5050505050505050565b610318610626565b6000816001600160a01b03164760405160006040518083038185875af1925050503d8060008114610365576040519150601f19603f3d011682016040523d82523d6000602084013e61036a565b606091505b50509050806103b25760405162461bcd60e51b815260206004820152600f60248201526e1dda5d1a191c985dc819985a5b1959608a1b60448201526064015b60405180910390fd5b5050565b6103be610626565b60038190556040518181527f20461e09b8e557b77e107939f9ce6544698123aad0fc964ac5cc59b7df2e608f906020015b60405180910390a150565b610402610626565b61040c6000610653565b565b60007ff266d2fc560c9aaf31e6bdf020cac0f2a9d5b3aa16f932e16434749ddff1499961043e60208601866108ff565b6040860135610453608088016060890161091a565b61046360a0890160808a01610807565b6040805163ffffffff959095168552602085019390935267ffffffffffffffff91909116838301526001600160a01b03166060830152519081900360800190a1506003549392505050565b6104b6610626565b6001600160a01b0381166104fc5760405162461bcd60e51b815260206004820152600d60248201526c3d32b9379037b832b930ba37b960991b60448201526064016103a9565b600180546001600160a01b0319166001600160a01b0383169081179091556040519081527f99d737e0adf2c449d71890b86772885ec7959b152ddb265f76325b6e68e105d3906020016103ef565b610552610626565b6001600160a01b03811661059a5760405162461bcd60e51b815260206004820152600f60248201526e3d32b937903932b1b2b4bb32aab63760891b60448201526064016103a9565b600280546001600160a01b0319166001600160a01b0383169081179091556040519081527f549e7b08606f710537aa4c9f00cb4d403d0048a8049411f3c1922d6e97744d6a906020016103ef565b6105f0610626565b6001600160a01b03811661061a57604051631e4fbdf760e01b8152600060048201526024016103a9565b61062381610653565b50565b6000546001600160a01b0316331461040c5760405163118cdaa760e01b81523360048201526024016103a9565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b803563ffffffff811681146106b757600080fd5b919050565b803567ffffffffffffffff811681146106b757600080fd5b6001600160a01b038116811461062357600080fd5b60008083601f8401126106fb57600080fd5b50813567ffffffffffffffff81111561071357600080fd5b60208301915083602082850101111561072b57600080fd5b9250929050565b60008060008060006080868803121561074a57600080fd5b610753866106a3565b9450610761602087016106bc565b93506040860135610771816106d4565b9250606086013567ffffffffffffffff81111561078d57600080fd5b610799888289016106e9565b969995985093965092949392505050565b600080600080606085870312156107c057600080fd5b843567ffffffffffffffff8111156107d757600080fd5b6107e3878288016106e9565b909550935050602085013591506107fc604086016106bc565b905092959194509250565b60006020828403121561081957600080fd5b8135610824816106d4565b9392505050565b60006020828403121561083d57600080fd5b5035919050565b60008060006040848603121561085957600080fd5b833567ffffffffffffffff8082111561087157600080fd5b9085019060a0828803121561088557600080fd5b9093506020850135908082111561089b57600080fd5b506108a8868287016106e9565b9497909650939450505050565b606081528360608201528385608083013760006080858301015260006080601f19601f870116830101905083602083015267ffffffffffffffff8316604083015295945050505050565b60006020828403121561091157600080fd5b610824826106a3565b60006020828403121561092c57600080fd5b610824826106bc56fea26469706673582212200429577ba4787f70d95d6de684082e4a59ee8692b228009a92be0dbe0d2d237164736f6c63430008160033", + "numDeployments": 3, + "solcInputHash": "97ae1cbcc67ee4ffae50031ebd6ec920", + "metadata": "{\"compiler\":{\"version\":\"0.8.22+commit.4fc1097e\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_operator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_sendUln\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_receiveUln\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_fee\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"AllowNotSeparatelyRecorded\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotOperator\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotSendLibrary\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"OwnableInvalidOwner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"OwnableUnauthorizedAccount\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"action\",\"type\":\"uint8\"}],\"name\":\"UnknownAction\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"action\",\"type\":\"uint8\"}],\"name\":\"VerificationRequiresAllow\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"fee\",\"type\":\"uint256\"}],\"name\":\"FeeSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"payloadHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"confirmations\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"JobAssigned\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"}],\"name\":\"OperatorSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"payloadHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"approver\",\"type\":\"address\"}],\"name\":\"PacketApproved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"receiveUln\",\"type\":\"address\"}],\"name\":\"ReceiveUlnSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"payloadHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"action\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"score\",\"type\":\"uint16\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"reasonMask\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"evidenceHash\",\"type\":\"bytes32\"}],\"name\":\"RiskVerdict\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"sendUln\",\"type\":\"address\"}],\"name\":\"SendUlnSet\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"ACTION_ALLOW\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"ACTION_BLOCK\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"ACTION_DELAY\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"ACTION_MANUAL_REVIEW\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"payloadHash\",\"type\":\"bytes32\"}],\"name\":\"approvePacket\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"packetHeader\",\"type\":\"bytes\"},{\"internalType\":\"bytes32\",\"name\":\"payloadHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"confirmations\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"internalType\":\"struct ILayerZeroDVN.AssignJobParam\",\"name\":\"_param\",\"type\":\"tuple\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"name\":\"assignJob\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"fee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"},{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"name\":\"getFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"operator\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"receiveUln\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"payloadHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint8\",\"name\":\"action\",\"type\":\"uint8\"},{\"internalType\":\"uint16\",\"name\":\"score\",\"type\":\"uint16\"},{\"internalType\":\"uint256\",\"name\":\"reasonMask\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"evidenceHash\",\"type\":\"bytes32\"}],\"name\":\"recordVerdict\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"sendUln\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_fee\",\"type\":\"uint256\"}],\"name\":\"setFee\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_operator\",\"type\":\"address\"}],\"name\":\"setOperator\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_receiveUln\",\"type\":\"address\"}],\"name\":\"setReceiveUln\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_sendUln\",\"type\":\"address\"}],\"name\":\"setSendUln\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"packetHeader\",\"type\":\"bytes\"},{\"internalType\":\"bytes32\",\"name\":\"payloadHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"confirmations\",\"type\":\"uint64\"},{\"internalType\":\"uint8\",\"name\":\"action\",\"type\":\"uint8\"},{\"internalType\":\"uint16\",\"name\":\"score\",\"type\":\"uint16\"},{\"internalType\":\"uint256\",\"name\":\"reasonMask\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"evidenceHash\",\"type\":\"bytes32\"}],\"name\":\"submitVerification\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address payable\",\"name\":\"_to\",\"type\":\"address\"}],\"name\":\"withdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"errors\":{\"AllowNotSeparatelyRecorded()\":[{\"details\":\"An allow rides along on `submitVerification`, so recording one separately would double-report the same outcome.\"}],\"OwnableInvalidOwner(address)\":[{\"details\":\"The owner is not a valid owner account. (eg. `address(0)`)\"}],\"OwnableUnauthorizedAccount(address)\":[{\"details\":\"The caller account is not authorized to perform an operation.\"}],\"VerificationRequiresAllow(uint8)\":[{\"details\":\"Submitting a verification asserts the packet was allowed; any other action would be a self-contradicting record.\"}]},\"events\":{\"RiskVerdict(bytes32,uint8,uint16,uint256,bytes32)\":{\"params\":{\"action\":\"ACTION_* below\",\"evidenceHash\":\"keccak256 of the canonical evidence document held off-chain\",\"payloadHash\":\"the packet this verdict is about\",\"reasonMask\":\"bitmask of reason codes; bit assignments are append-only and documented in the worker's `assess/verdict.ts`\",\"score\":\"0-100 risk score the action was derived from\"}}},\"kind\":\"dev\",\"methods\":{\"approvePacket(bytes32)\":{\"details\":\"Emits only; no storage. The worker observes `PacketApproved` and releases the packet from its local deferred queue. Approval is a human override of a risk verdict, so it is separated from the operator key by design \\u2014 a compromised or buggy worker cannot approve the packets it chose to hold.\"},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"recordVerdict(bytes32,uint8,uint16,uint256,bytes32)\":{\"details\":\"Withholding the attestation is what actually stops the packet; this only leaves the audit trail. It is therefore best-effort by design \\u2014 the worker treats a failure here as a lost record, never as a failure to enforce.\"},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner.\"},\"submitVerification(bytes,bytes32,uint64,uint8,uint16,uint256,bytes32)\":{\"details\":\"The verdict rides along at no extra transaction cost, so an allowed packet always carries an auditable reason for having been allowed. `action` must be ACTION_ALLOW: a packet that was blocked or held cannot also have been verified. An owner-approved release is reported as ACTION_ALLOW too \\u2014 a human allowed it \\u2014 with the reason mask still carrying why it had been held.\"},\"transferOwnership(address)\":{\"details\":\"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.\"}},\"stateVariables\":{\"ACTION_ALLOW\":{\"details\":\"Action codes. These are part of the event ABI: an indexer decoding old logs relies on them, so the numbering is permanent. Kept in sync with the worker's ACTION_CODES.\"}},\"title\":\"ComplianceDVN\",\"version\":1},\"userdoc\":{\"events\":{\"PacketApproved(bytes32,address)\":{\"notice\":\"A held packet cleared for verification by the owner. Deliberately owner-only: the worker holds only the operator key, so it cannot approve its own holds.\"},\"RiskVerdict(bytes32,uint8,uint16,uint256,bytes32)\":{\"notice\":\"The risk decision behind a packet's outcome.\"}},\"kind\":\"user\",\"methods\":{\"approvePacket(bytes32)\":{\"notice\":\"Clear a packet the worker withheld for manual review.\"},\"recordVerdict(bytes32,uint8,uint16,uint256,bytes32)\":{\"notice\":\"Record a verdict for a packet that was NOT verified.\"},\"submitVerification(bytes,bytes32,uint64,uint8,uint16,uint256,bytes32)\":{\"notice\":\"Attest a packet and record the risk verdict that permitted it, in one call.\"}},\"notice\":\"Thin LayerZero V2 DVN. All compliance judgment is off-chain; the on-chain contract only conforms to the worker-job interface and gates the destination attestation behind an operator key. Withholding `submitVerification` IS the veto.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/ComplianceDVN.sol\":\"ComplianceDVN\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/ILayerZeroDVN.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\ninterface ILayerZeroDVN {\\n struct AssignJobParam {\\n uint32 dstEid;\\n bytes packetHeader;\\n bytes32 payloadHash;\\n uint64 confirmations;\\n address sender;\\n }\\n\\n // @notice query price and assign jobs at the same time\\n // @param _dstEid - the destination endpoint identifier\\n // @param _packetHeader - version + nonce + path\\n // @param _payloadHash - hash of guid + message\\n // @param _confirmations - block confirmation delay before relaying blocks\\n // @param _sender - the source sending contract address\\n // @param _options - options\\n function assignJob(AssignJobParam calldata _param, bytes calldata _options) external payable returns (uint256 fee);\\n\\n // @notice query the dvn fee for relaying block information to the destination chain\\n // @param _dstEid the destination endpoint identifier\\n // @param _confirmations - block confirmation delay before relaying blocks\\n // @param _sender - the source sending contract address\\n // @param _options - options\\n function getFee(\\n uint32 _dstEid,\\n uint64 _confirmations,\\n address _sender,\\n bytes calldata _options\\n ) external view returns (uint256 fee);\\n}\\n\",\"keccak256\":\"0x308e77078242fd5c5746ec29c12e618249134f9e4377c0028ab8f59c07a6014b\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/IReceiveUlnE2.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\n/// @dev should be implemented by the ReceiveUln302 contract and future ReceiveUln contracts on EndpointV2\\ninterface IReceiveUlnE2 {\\n /// @notice for each dvn to verify the payload\\n /// @dev this function signature 0x0223536e\\n function verify(bytes calldata _packetHeader, bytes32 _payloadHash, uint64 _confirmations) external;\\n\\n /// @notice verify the payload at endpoint, will check if all DVNs verified\\n function commitVerification(bytes calldata _packetHeader, bytes32 _payloadHash) external;\\n}\\n\",\"keccak256\":\"0xcdf7e690e5d5c0a3ec26a0d7b1a7fe49c7d16a3634721c3944f77d13ff5d4a91\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/Ownable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\\n\\npragma solidity ^0.8.20;\\n\\nimport {Context} from \\\"../utils/Context.sol\\\";\\n\\n/**\\n * @dev Contract module which provides a basic access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * The initial owner is set to the address provided by the deployer. This can\\n * later be changed with {transferOwnership}.\\n *\\n * This module is used through inheritance. It will make available the modifier\\n * `onlyOwner`, which can be applied to your functions to restrict their use to\\n * the owner.\\n */\\nabstract contract Ownable is Context {\\n address private _owner;\\n\\n /**\\n * @dev The caller account is not authorized to perform an operation.\\n */\\n error OwnableUnauthorizedAccount(address account);\\n\\n /**\\n * @dev The owner is not a valid owner account. (eg. `address(0)`)\\n */\\n error OwnableInvalidOwner(address owner);\\n\\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\\n\\n /**\\n * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\\n */\\n constructor(address initialOwner) {\\n if (initialOwner == address(0)) {\\n revert OwnableInvalidOwner(address(0));\\n }\\n _transferOwnership(initialOwner);\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the owner.\\n */\\n modifier onlyOwner() {\\n _checkOwner();\\n _;\\n }\\n\\n /**\\n * @dev Returns the address of the current owner.\\n */\\n function owner() public view virtual returns (address) {\\n return _owner;\\n }\\n\\n /**\\n * @dev Throws if the sender is not the owner.\\n */\\n function _checkOwner() internal view virtual {\\n if (owner() != _msgSender()) {\\n revert OwnableUnauthorizedAccount(_msgSender());\\n }\\n }\\n\\n /**\\n * @dev Leaves the contract without owner. It will not be possible to call\\n * `onlyOwner` functions. Can only be called by the current owner.\\n *\\n * NOTE: Renouncing ownership will leave the contract without an owner,\\n * thereby disabling any functionality that is only available to the owner.\\n */\\n function renounceOwnership() public virtual onlyOwner {\\n _transferOwnership(address(0));\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual onlyOwner {\\n if (newOwner == address(0)) {\\n revert OwnableInvalidOwner(address(0));\\n }\\n _transferOwnership(newOwner);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Internal function without access restriction.\\n */\\n function _transferOwnership(address newOwner) internal virtual {\\n address oldOwner = _owner;\\n _owner = newOwner;\\n emit OwnershipTransferred(oldOwner, newOwner);\\n }\\n}\\n\",\"keccak256\":\"0xff6d0bb2e285473e5311d9d3caacb525ae3538a80758c10649a4d61029b017bb\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Context.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\\n\\npragma solidity ^0.8.20;\\n\\n/**\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract Context {\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n\\n function _contextSuffixLength() internal view virtual returns (uint256) {\\n return 0;\\n }\\n}\\n\",\"keccak256\":\"0x493033a8d1b176a037b2cc6a04dad01a5c157722049bbecf632ca876224dd4b2\",\"license\":\"MIT\"},\"contracts/ComplianceDVN.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\r\\npragma solidity ^0.8.22;\\r\\n\\r\\nimport { Ownable } from \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\r\\nimport { ILayerZeroDVN } from \\\"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/ILayerZeroDVN.sol\\\";\\r\\nimport { IReceiveUlnE2 } from \\\"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/IReceiveUlnE2.sol\\\";\\r\\n\\r\\n/// @title ComplianceDVN\\r\\n/// @notice Thin LayerZero V2 DVN. All compliance judgment is off-chain; the on-chain\\r\\n/// contract only conforms to the worker-job interface and gates the destination\\r\\n/// attestation behind an operator key. Withholding `submitVerification` IS the veto.\\r\\ncontract ComplianceDVN is ILayerZeroDVN, Ownable {\\r\\n address public operator; // off-chain worker key\\r\\n address public sendUln; // SendUln302 on this chain \\u2014 the only address allowed to assign jobs\\r\\n address public receiveUln; // ReceiveUln302 on this chain\\r\\n uint256 public fee;\\r\\n\\r\\n event JobAssigned(uint32 dstEid, bytes32 payloadHash, uint64 confirmations, address sender);\\r\\n event OperatorSet(address operator);\\r\\n event SendUlnSet(address sendUln);\\r\\n event ReceiveUlnSet(address receiveUln);\\r\\n event FeeSet(uint256 fee);\\r\\n\\r\\n /// @notice A held packet cleared for verification by the owner. Deliberately owner-only:\\r\\n /// the worker holds only the operator key, so it cannot approve its own holds.\\r\\n event PacketApproved(bytes32 indexed payloadHash, address approver);\\r\\n\\r\\n /// @notice The risk decision behind a packet's outcome.\\r\\n /// @param payloadHash the packet this verdict is about\\r\\n /// @param action ACTION_* below\\r\\n /// @param score 0-100 risk score the action was derived from\\r\\n /// @param reasonMask bitmask of reason codes; bit assignments are append-only and\\r\\n /// documented in the worker's `assess/verdict.ts`\\r\\n /// @param evidenceHash keccak256 of the canonical evidence document held off-chain\\r\\n event RiskVerdict(\\r\\n bytes32 indexed payloadHash,\\r\\n uint8 action,\\r\\n uint16 score,\\r\\n uint256 reasonMask,\\r\\n bytes32 evidenceHash\\r\\n );\\r\\n\\r\\n /// @dev Action codes. These are part of the event ABI: an indexer decoding old logs relies\\r\\n /// on them, so the numbering is permanent. Kept in sync with the worker's ACTION_CODES.\\r\\n uint8 public constant ACTION_ALLOW = 0;\\r\\n uint8 public constant ACTION_DELAY = 1;\\r\\n uint8 public constant ACTION_MANUAL_REVIEW = 2;\\r\\n uint8 public constant ACTION_BLOCK = 3;\\r\\n\\r\\n error NotOperator();\\r\\n error NotSendLibrary();\\r\\n error UnknownAction(uint8 action);\\r\\n /// @dev Submitting a verification asserts the packet was allowed; any other action would be\\r\\n /// a self-contradicting record.\\r\\n error VerificationRequiresAllow(uint8 action);\\r\\n /// @dev An allow rides along on `submitVerification`, so recording one separately would\\r\\n /// double-report the same outcome.\\r\\n error AllowNotSeparatelyRecorded();\\r\\n\\r\\n modifier onlyOperator() {\\r\\n if (msg.sender != operator) revert NotOperator();\\r\\n _;\\r\\n }\\r\\n\\r\\n constructor(\\r\\n address _owner,\\r\\n address _operator,\\r\\n address _sendUln,\\r\\n address _receiveUln,\\r\\n uint256 _fee\\r\\n ) Ownable(_owner) {\\r\\n require(_operator != address(0), \\\"zero operator\\\");\\r\\n require(_sendUln != address(0), \\\"zero sendUln\\\");\\r\\n require(_receiveUln != address(0), \\\"zero receiveUln\\\");\\r\\n operator = _operator;\\r\\n sendUln = _sendUln;\\r\\n receiveUln = _receiveUln;\\r\\n fee = _fee;\\r\\n }\\r\\n\\r\\n function getFee(\\r\\n uint32 /*_dstEid*/,\\r\\n uint64 /*_confirmations*/,\\r\\n address /*_sender*/,\\r\\n bytes calldata /*_options*/\\r\\n ) external view returns (uint256) {\\r\\n return fee;\\r\\n }\\r\\n\\r\\n function assignJob(AssignJobParam calldata _param, bytes calldata) external payable returns (uint256) {\\r\\n // Only the send library assigns jobs. The worker treats a JobAssigned payloadHash as\\r\\n // \\\"this packet is ours to screen\\\" and spends operator gas verifying it, so an open\\r\\n // assignJob would let anyone point the worker at packets no one asked it to verify.\\r\\n if (msg.sender != sendUln) revert NotSendLibrary();\\r\\n // NOTE: SendUln302 calls assignJob WITHOUT forwarding value (msg.value == 0); the\\r\\n // messagelib accrues each worker's fee internally and workers withdraw separately\\r\\n // (see SendUlnBase._assignJobs). So we must NOT require msg.value >= fee here \\u2014 doing\\r\\n // so reverts every real send. We simply record the job and return our fee quote.\\r\\n emit JobAssigned(_param.dstEid, _param.payloadHash, _param.confirmations, _param.sender);\\r\\n return fee;\\r\\n }\\r\\n\\r\\n /// @notice Attest a packet and record the risk verdict that permitted it, in one call.\\r\\n /// @dev The verdict rides along at no extra transaction cost, so an allowed packet always\\r\\n /// carries an auditable reason for having been allowed. `action` must be ACTION_ALLOW:\\r\\n /// a packet that was blocked or held cannot also have been verified. An owner-approved\\r\\n /// release is reported as ACTION_ALLOW too \\u2014 a human allowed it \\u2014 with the reason mask\\r\\n /// still carrying why it had been held.\\r\\n function submitVerification(\\r\\n bytes calldata packetHeader,\\r\\n bytes32 payloadHash,\\r\\n uint64 confirmations,\\r\\n uint8 action,\\r\\n uint16 score,\\r\\n uint256 reasonMask,\\r\\n bytes32 evidenceHash\\r\\n ) external onlyOperator {\\r\\n if (action != ACTION_ALLOW) revert VerificationRequiresAllow(action);\\r\\n IReceiveUlnE2(receiveUln).verify(packetHeader, payloadHash, confirmations);\\r\\n emit RiskVerdict(payloadHash, action, score, reasonMask, evidenceHash);\\r\\n }\\r\\n\\r\\n /// @notice Record a verdict for a packet that was NOT verified.\\r\\n /// @dev Withholding the attestation is what actually stops the packet; this only leaves the\\r\\n /// audit trail. It is therefore best-effort by design \\u2014 the worker treats a failure\\r\\n /// here as a lost record, never as a failure to enforce.\\r\\n function recordVerdict(\\r\\n bytes32 payloadHash,\\r\\n uint8 action,\\r\\n uint16 score,\\r\\n uint256 reasonMask,\\r\\n bytes32 evidenceHash\\r\\n ) external onlyOperator {\\r\\n if (action > ACTION_BLOCK) revert UnknownAction(action);\\r\\n if (action == ACTION_ALLOW) revert AllowNotSeparatelyRecorded();\\r\\n emit RiskVerdict(payloadHash, action, score, reasonMask, evidenceHash);\\r\\n }\\r\\n\\r\\n /// @notice Clear a packet the worker withheld for manual review.\\r\\n /// @dev Emits only; no storage. The worker observes `PacketApproved` and releases the\\r\\n /// packet from its local deferred queue. Approval is a human override of a risk\\r\\n /// verdict, so it is separated from the operator key by design \\u2014 a compromised or\\r\\n /// buggy worker cannot approve the packets it chose to hold.\\r\\n function approvePacket(bytes32 payloadHash) external onlyOwner {\\r\\n emit PacketApproved(payloadHash, msg.sender);\\r\\n }\\r\\n\\r\\n function setOperator(address _operator) external onlyOwner {\\r\\n require(_operator != address(0), \\\"zero operator\\\");\\r\\n operator = _operator;\\r\\n emit OperatorSet(_operator);\\r\\n }\\r\\n\\r\\n function setSendUln(address _sendUln) external onlyOwner {\\r\\n require(_sendUln != address(0), \\\"zero sendUln\\\");\\r\\n sendUln = _sendUln;\\r\\n emit SendUlnSet(_sendUln);\\r\\n }\\r\\n\\r\\n function setReceiveUln(address _receiveUln) external onlyOwner {\\r\\n require(_receiveUln != address(0), \\\"zero receiveUln\\\");\\r\\n receiveUln = _receiveUln;\\r\\n emit ReceiveUlnSet(_receiveUln);\\r\\n }\\r\\n\\r\\n function setFee(uint256 _fee) external onlyOwner {\\r\\n fee = _fee;\\r\\n emit FeeSet(_fee);\\r\\n }\\r\\n\\r\\n function withdraw(address payable _to) external onlyOwner {\\r\\n (bool ok, ) = _to.call{ value: address(this).balance }(\\\"\\\");\\r\\n require(ok, \\\"withdraw failed\\\");\\r\\n }\\r\\n}\\r\\n\",\"keccak256\":\"0x87439dd615ec1afef7dbbc323b152defa47f21292bf80d472faf345559b18f5a\",\"license\":\"MIT\"}},\"version\":1}", + "bytecode": "0x60806040523480156200001157600080fd5b5060405162001022380380620010228339810160408190526200003491620001fe565b846001600160a01b0381166200006557604051631e4fbdf760e01b8152600060048201526024015b60405180910390fd5b620000708162000191565b506001600160a01b038416620000b95760405162461bcd60e51b815260206004820152600d60248201526c3d32b9379037b832b930ba37b960991b60448201526064016200005c565b6001600160a01b038316620001005760405162461bcd60e51b815260206004820152600c60248201526b3d32b9379039b2b7322ab63760a11b60448201526064016200005c565b6001600160a01b0382166200014a5760405162461bcd60e51b815260206004820152600f60248201526e3d32b937903932b1b2b4bb32aab63760891b60448201526064016200005c565b600180546001600160a01b03199081166001600160a01b03968716179091556002805482169486169490941790935560038054909316919093161790556004555062000265565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b80516001600160a01b0381168114620001f957600080fd5b919050565b600080600080600060a086880312156200021757600080fd5b6200022286620001e1565b94506200023260208701620001e1565b93506200024260408701620001e1565b92506200025260608701620001e1565b9150608086015190509295509295909350565b610dad80620002756000396000f3fe60806040526004361061012a5760003560e01c80638337a03f116100ab578063a3be73e81161006f578063a3be73e81461031a578063b3ab15fb1461032f578063ddca3f431461034f578063e3b6f56714610365578063e49ca47114610385578063f2fde38b146103a557600080fd5b80638337a03f1461029457806389861756146102b45780638da5cb5b146102c95780638f16020a146102e757806395d376d71461030757600080fd5b8063570ca735116100f2578063570ca7351461020a578063621d665d1461022a57806369fe0e2d1461024a578063715018a61461026a57806376ab3b431461027f57600080fd5b80630b3448f21461012f5780630d54d7071461016c57806330bb3aac1461018e57806337610b1b146101c357806351cff8d9146101ea575b600080fd5b34801561013b57600080fd5b5060025461014f906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561017857600080fd5b5061018c610187366004610ac9565b6103c5565b005b34801561019a57600080fd5b506101b56101a9366004610b83565b60045495945050505050565b604051908152602001610163565b3480156101cf57600080fd5b506101d8600381565b60405160ff9091168152602001610163565b3480156101f657600080fd5b5061018c610205366004610bfb565b6104de565b34801561021657600080fd5b5060015461014f906001600160a01b031681565b34801561023657600080fd5b5061018c610245366004610c1f565b61057f565b34801561025657600080fd5b5061018c610265366004610c1f565b6105bf565b34801561027657600080fd5b5061018c610603565b34801561028b57600080fd5b506101d8600081565b3480156102a057600080fd5b5061018c6102af366004610bfb565b610617565b3480156102c057600080fd5b506101d8600281565b3480156102d557600080fd5b506000546001600160a01b031661014f565b3480156102f357600080fd5b5061018c610302366004610c38565b6106b2565b6101b5610315366004610c86565b61077f565b34801561032657600080fd5b506101d8600181565b34801561033b57600080fd5b5061018c61034a366004610bfb565b61084b565b34801561035b57600080fd5b506101b560045481565b34801561037157600080fd5b5061018c610380366004610bfb565b6108e7565b34801561039157600080fd5b5060035461014f906001600160a01b031681565b3480156103b157600080fd5b5061018c6103c0366004610bfb565b610985565b6001546001600160a01b031633146103f057604051631f0853c160e21b815260040160405180910390fd5b60ff84161561041c5760405163a0940ea960e01b815260ff851660048201526024015b60405180910390fd5b600354604051630111a9b760e11b81526001600160a01b0390911690630223536e90610452908b908b908b908b90600401610cf7565b600060405180830381600087803b15801561046c57600080fd5b505af1158015610480573d6000803e3d6000fd5b50506040805160ff8816815261ffff87166020820152908101859052606081018490528892507f58ec8d75f99798b0f5d8fe567e102c2006f27041b28ccf9c65e27c8ba010f807915060800160405180910390a25050505050505050565b6104e66109c3565b6000816001600160a01b03164760405160006040518083038185875af1925050503d8060008114610533576040519150601f19603f3d011682016040523d82523d6000602084013e610538565b606091505b505090508061057b5760405162461bcd60e51b815260206004820152600f60248201526e1dda5d1a191c985dc819985a5b1959608a1b6044820152606401610413565b5050565b6105876109c3565b60405133815281907feef04c31cb4b8b2e7ade37c9f258844c21380c82fad1b9af9529dc3b1326daba9060200160405180910390a250565b6105c76109c3565b60048190556040518181527f20461e09b8e557b77e107939f9ce6544698123aad0fc964ac5cc59b7df2e608f906020015b60405180910390a150565b61060b6109c3565b61061560006109f0565b565b61061f6109c3565b6001600160a01b0381166106645760405162461bcd60e51b815260206004820152600c60248201526b3d32b9379039b2b7322ab63760a11b6044820152606401610413565b600280546001600160a01b0319166001600160a01b0383169081179091556040519081527f81fee7bca94bea3e3e3560b9e8019e639c980511659ce436bd6e62790dbddbbd906020016105f8565b6001546001600160a01b031633146106dd57604051631f0853c160e21b815260040160405180910390fd5b600360ff85161115610707576040516360df9f8760e01b815260ff85166004820152602401610413565b60ff8416610728576040516330e4fe0360e11b815260040160405180910390fd5b6040805160ff8616815261ffff851660208201529081018390526060810182905285907f58ec8d75f99798b0f5d8fe567e102c2006f27041b28ccf9c65e27c8ba010f8079060800160405180910390a25050505050565b6002546000906001600160a01b031633146107ad57604051637f2e104960e01b815260040160405180910390fd5b7ff266d2fc560c9aaf31e6bdf020cac0f2a9d5b3aa16f932e16434749ddff149996107db6020860186610d41565b60408601356107f06080880160608901610d5c565b61080060a0890160808a01610bfb565b6040805163ffffffff959095168552602085019390935267ffffffffffffffff91909116838301526001600160a01b03166060830152519081900360800190a1506004549392505050565b6108536109c3565b6001600160a01b0381166108995760405162461bcd60e51b815260206004820152600d60248201526c3d32b9379037b832b930ba37b960991b6044820152606401610413565b600180546001600160a01b0319166001600160a01b0383169081179091556040519081527f99d737e0adf2c449d71890b86772885ec7959b152ddb265f76325b6e68e105d3906020016105f8565b6108ef6109c3565b6001600160a01b0381166109375760405162461bcd60e51b815260206004820152600f60248201526e3d32b937903932b1b2b4bb32aab63760891b6044820152606401610413565b600380546001600160a01b0319166001600160a01b0383169081179091556040519081527f549e7b08606f710537aa4c9f00cb4d403d0048a8049411f3c1922d6e97744d6a906020016105f8565b61098d6109c3565b6001600160a01b0381166109b757604051631e4fbdf760e01b815260006004820152602401610413565b6109c0816109f0565b50565b6000546001600160a01b031633146106155760405163118cdaa760e01b8152336004820152602401610413565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60008083601f840112610a5257600080fd5b50813567ffffffffffffffff811115610a6a57600080fd5b602083019150836020828501011115610a8257600080fd5b9250929050565b803567ffffffffffffffff81168114610aa157600080fd5b919050565b803560ff81168114610aa157600080fd5b803561ffff81168114610aa157600080fd5b60008060008060008060008060e0898b031215610ae557600080fd5b883567ffffffffffffffff811115610afc57600080fd5b610b088b828c01610a40565b90995097505060208901359550610b2160408a01610a89565b9450610b2f60608a01610aa6565b9350610b3d60808a01610ab7565b925060a0890135915060c089013590509295985092959890939650565b803563ffffffff81168114610aa157600080fd5b6001600160a01b03811681146109c057600080fd5b600080600080600060808688031215610b9b57600080fd5b610ba486610b5a565b9450610bb260208701610a89565b93506040860135610bc281610b6e565b9250606086013567ffffffffffffffff811115610bde57600080fd5b610bea88828901610a40565b969995985093965092949392505050565b600060208284031215610c0d57600080fd5b8135610c1881610b6e565b9392505050565b600060208284031215610c3157600080fd5b5035919050565b600080600080600060a08688031215610c5057600080fd5b85359450610c6060208701610aa6565b9350610c6e60408701610ab7565b94979396509394606081013594506080013592915050565b600080600060408486031215610c9b57600080fd5b833567ffffffffffffffff80821115610cb357600080fd5b9085019060a08288031215610cc757600080fd5b90935060208501359080821115610cdd57600080fd5b50610cea86828701610a40565b9497909650939450505050565b606081528360608201528385608083013760006080858301015260006080601f19601f870116830101905083602083015267ffffffffffffffff8316604083015295945050505050565b600060208284031215610d5357600080fd5b610c1882610b5a565b600060208284031215610d6e57600080fd5b610c1882610a8956fea2646970667358221220d94d4a34a82171ea20ebdc7b4651dff6411ce021ee5097ce9daee18d8b3512c364736f6c63430008160033", + "deployedBytecode": "0x60806040526004361061012a5760003560e01c80638337a03f116100ab578063a3be73e81161006f578063a3be73e81461031a578063b3ab15fb1461032f578063ddca3f431461034f578063e3b6f56714610365578063e49ca47114610385578063f2fde38b146103a557600080fd5b80638337a03f1461029457806389861756146102b45780638da5cb5b146102c95780638f16020a146102e757806395d376d71461030757600080fd5b8063570ca735116100f2578063570ca7351461020a578063621d665d1461022a57806369fe0e2d1461024a578063715018a61461026a57806376ab3b431461027f57600080fd5b80630b3448f21461012f5780630d54d7071461016c57806330bb3aac1461018e57806337610b1b146101c357806351cff8d9146101ea575b600080fd5b34801561013b57600080fd5b5060025461014f906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561017857600080fd5b5061018c610187366004610ac9565b6103c5565b005b34801561019a57600080fd5b506101b56101a9366004610b83565b60045495945050505050565b604051908152602001610163565b3480156101cf57600080fd5b506101d8600381565b60405160ff9091168152602001610163565b3480156101f657600080fd5b5061018c610205366004610bfb565b6104de565b34801561021657600080fd5b5060015461014f906001600160a01b031681565b34801561023657600080fd5b5061018c610245366004610c1f565b61057f565b34801561025657600080fd5b5061018c610265366004610c1f565b6105bf565b34801561027657600080fd5b5061018c610603565b34801561028b57600080fd5b506101d8600081565b3480156102a057600080fd5b5061018c6102af366004610bfb565b610617565b3480156102c057600080fd5b506101d8600281565b3480156102d557600080fd5b506000546001600160a01b031661014f565b3480156102f357600080fd5b5061018c610302366004610c38565b6106b2565b6101b5610315366004610c86565b61077f565b34801561032657600080fd5b506101d8600181565b34801561033b57600080fd5b5061018c61034a366004610bfb565b61084b565b34801561035b57600080fd5b506101b560045481565b34801561037157600080fd5b5061018c610380366004610bfb565b6108e7565b34801561039157600080fd5b5060035461014f906001600160a01b031681565b3480156103b157600080fd5b5061018c6103c0366004610bfb565b610985565b6001546001600160a01b031633146103f057604051631f0853c160e21b815260040160405180910390fd5b60ff84161561041c5760405163a0940ea960e01b815260ff851660048201526024015b60405180910390fd5b600354604051630111a9b760e11b81526001600160a01b0390911690630223536e90610452908b908b908b908b90600401610cf7565b600060405180830381600087803b15801561046c57600080fd5b505af1158015610480573d6000803e3d6000fd5b50506040805160ff8816815261ffff87166020820152908101859052606081018490528892507f58ec8d75f99798b0f5d8fe567e102c2006f27041b28ccf9c65e27c8ba010f807915060800160405180910390a25050505050505050565b6104e66109c3565b6000816001600160a01b03164760405160006040518083038185875af1925050503d8060008114610533576040519150601f19603f3d011682016040523d82523d6000602084013e610538565b606091505b505090508061057b5760405162461bcd60e51b815260206004820152600f60248201526e1dda5d1a191c985dc819985a5b1959608a1b6044820152606401610413565b5050565b6105876109c3565b60405133815281907feef04c31cb4b8b2e7ade37c9f258844c21380c82fad1b9af9529dc3b1326daba9060200160405180910390a250565b6105c76109c3565b60048190556040518181527f20461e09b8e557b77e107939f9ce6544698123aad0fc964ac5cc59b7df2e608f906020015b60405180910390a150565b61060b6109c3565b61061560006109f0565b565b61061f6109c3565b6001600160a01b0381166106645760405162461bcd60e51b815260206004820152600c60248201526b3d32b9379039b2b7322ab63760a11b6044820152606401610413565b600280546001600160a01b0319166001600160a01b0383169081179091556040519081527f81fee7bca94bea3e3e3560b9e8019e639c980511659ce436bd6e62790dbddbbd906020016105f8565b6001546001600160a01b031633146106dd57604051631f0853c160e21b815260040160405180910390fd5b600360ff85161115610707576040516360df9f8760e01b815260ff85166004820152602401610413565b60ff8416610728576040516330e4fe0360e11b815260040160405180910390fd5b6040805160ff8616815261ffff851660208201529081018390526060810182905285907f58ec8d75f99798b0f5d8fe567e102c2006f27041b28ccf9c65e27c8ba010f8079060800160405180910390a25050505050565b6002546000906001600160a01b031633146107ad57604051637f2e104960e01b815260040160405180910390fd5b7ff266d2fc560c9aaf31e6bdf020cac0f2a9d5b3aa16f932e16434749ddff149996107db6020860186610d41565b60408601356107f06080880160608901610d5c565b61080060a0890160808a01610bfb565b6040805163ffffffff959095168552602085019390935267ffffffffffffffff91909116838301526001600160a01b03166060830152519081900360800190a1506004549392505050565b6108536109c3565b6001600160a01b0381166108995760405162461bcd60e51b815260206004820152600d60248201526c3d32b9379037b832b930ba37b960991b6044820152606401610413565b600180546001600160a01b0319166001600160a01b0383169081179091556040519081527f99d737e0adf2c449d71890b86772885ec7959b152ddb265f76325b6e68e105d3906020016105f8565b6108ef6109c3565b6001600160a01b0381166109375760405162461bcd60e51b815260206004820152600f60248201526e3d32b937903932b1b2b4bb32aab63760891b6044820152606401610413565b600380546001600160a01b0319166001600160a01b0383169081179091556040519081527f549e7b08606f710537aa4c9f00cb4d403d0048a8049411f3c1922d6e97744d6a906020016105f8565b61098d6109c3565b6001600160a01b0381166109b757604051631e4fbdf760e01b815260006004820152602401610413565b6109c0816109f0565b50565b6000546001600160a01b031633146106155760405163118cdaa760e01b8152336004820152602401610413565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60008083601f840112610a5257600080fd5b50813567ffffffffffffffff811115610a6a57600080fd5b602083019150836020828501011115610a8257600080fd5b9250929050565b803567ffffffffffffffff81168114610aa157600080fd5b919050565b803560ff81168114610aa157600080fd5b803561ffff81168114610aa157600080fd5b60008060008060008060008060e0898b031215610ae557600080fd5b883567ffffffffffffffff811115610afc57600080fd5b610b088b828c01610a40565b90995097505060208901359550610b2160408a01610a89565b9450610b2f60608a01610aa6565b9350610b3d60808a01610ab7565b925060a0890135915060c089013590509295985092959890939650565b803563ffffffff81168114610aa157600080fd5b6001600160a01b03811681146109c057600080fd5b600080600080600060808688031215610b9b57600080fd5b610ba486610b5a565b9450610bb260208701610a89565b93506040860135610bc281610b6e565b9250606086013567ffffffffffffffff811115610bde57600080fd5b610bea88828901610a40565b969995985093965092949392505050565b600060208284031215610c0d57600080fd5b8135610c1881610b6e565b9392505050565b600060208284031215610c3157600080fd5b5035919050565b600080600080600060a08688031215610c5057600080fd5b85359450610c6060208701610aa6565b9350610c6e60408701610ab7565b94979396509394606081013594506080013592915050565b600080600060408486031215610c9b57600080fd5b833567ffffffffffffffff80821115610cb357600080fd5b9085019060a08288031215610cc757600080fd5b90935060208501359080821115610cdd57600080fd5b50610cea86828701610a40565b9497909650939450505050565b606081528360608201528385608083013760006080858301015260006080601f19601f870116830101905083602083015267ffffffffffffffff8316604083015295945050505050565b600060208284031215610d5357600080fd5b610c1882610b5a565b600060208284031215610d6e57600080fd5b610c1882610a8956fea2646970667358221220d94d4a34a82171ea20ebdc7b4651dff6411ce021ee5097ce9daee18d8b3512c364736f6c63430008160033", "devdoc": { "errors": { + "AllowNotSeparatelyRecorded()": [ + { + "details": "An allow rides along on `submitVerification`, so recording one separately would double-report the same outcome." + } + ], "OwnableInvalidOwner(address)": [ { "details": "The owner is not a valid owner account. (eg. `address(0)`)" @@ -429,26 +685,74 @@ { "details": "The caller account is not authorized to perform an operation." } + ], + "VerificationRequiresAllow(uint8)": [ + { + "details": "Submitting a verification asserts the packet was allowed; any other action would be a self-contradicting record." + } ] }, + "events": { + "RiskVerdict(bytes32,uint8,uint16,uint256,bytes32)": { + "params": { + "action": "ACTION_* below", + "evidenceHash": "keccak256 of the canonical evidence document held off-chain", + "payloadHash": "the packet this verdict is about", + "reasonMask": "bitmask of reason codes; bit assignments are append-only and documented in the worker's `assess/verdict.ts`", + "score": "0-100 risk score the action was derived from" + } + } + }, "kind": "dev", "methods": { + "approvePacket(bytes32)": { + "details": "Emits only; no storage. The worker observes `PacketApproved` and releases the packet from its local deferred queue. Approval is a human override of a risk verdict, so it is separated from the operator key by design — a compromised or buggy worker cannot approve the packets it chose to hold." + }, "owner()": { "details": "Returns the address of the current owner." }, + "recordVerdict(bytes32,uint8,uint16,uint256,bytes32)": { + "details": "Withholding the attestation is what actually stops the packet; this only leaves the audit trail. It is therefore best-effort by design — the worker treats a failure here as a lost record, never as a failure to enforce." + }, "renounceOwnership()": { "details": "Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner." }, + "submitVerification(bytes,bytes32,uint64,uint8,uint16,uint256,bytes32)": { + "details": "The verdict rides along at no extra transaction cost, so an allowed packet always carries an auditable reason for having been allowed. `action` must be ACTION_ALLOW: a packet that was blocked or held cannot also have been verified. An owner-approved release is reported as ACTION_ALLOW too — a human allowed it — with the reason mask still carrying why it had been held." + }, "transferOwnership(address)": { "details": "Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner." } }, + "stateVariables": { + "ACTION_ALLOW": { + "details": "Action codes. These are part of the event ABI: an indexer decoding old logs relies on them, so the numbering is permanent. Kept in sync with the worker's ACTION_CODES." + } + }, "title": "ComplianceDVN", "version": 1 }, "userdoc": { + "events": { + "PacketApproved(bytes32,address)": { + "notice": "A held packet cleared for verification by the owner. Deliberately owner-only: the worker holds only the operator key, so it cannot approve its own holds." + }, + "RiskVerdict(bytes32,uint8,uint16,uint256,bytes32)": { + "notice": "The risk decision behind a packet's outcome." + } + }, "kind": "user", - "methods": {}, + "methods": { + "approvePacket(bytes32)": { + "notice": "Clear a packet the worker withheld for manual review." + }, + "recordVerdict(bytes32,uint8,uint16,uint256,bytes32)": { + "notice": "Record a verdict for a packet that was NOT verified." + }, + "submitVerification(bytes,bytes32,uint64,uint8,uint16,uint256,bytes32)": { + "notice": "Attest a packet and record the risk verdict that permitted it, in one call." + } + }, "notice": "Thin LayerZero V2 DVN. All compliance judgment is off-chain; the on-chain contract only conforms to the worker-job interface and gates the destination attestation behind an operator key. Withholding `submitVerification` IS the veto.", "version": 1 }, @@ -473,7 +777,7 @@ { "astId": 253, "contract": "contracts/ComplianceDVN.sol:ComplianceDVN", - "label": "receiveUln", + "label": "sendUln", "offset": 0, "slot": "2", "type": "t_address" @@ -481,9 +785,17 @@ { "astId": 255, "contract": "contracts/ComplianceDVN.sol:ComplianceDVN", - "label": "fee", + "label": "receiveUln", "offset": 0, "slot": "3", + "type": "t_address" + }, + { + "astId": 257, + "contract": "contracts/ComplianceDVN.sol:ComplianceDVN", + "label": "fee", + "offset": 0, + "slot": "4", "type": "t_uint256" } ], diff --git a/deployments/optimism-sepolia/FakeStablecoinMock.json b/deployments/optimism-sepolia/FakeStablecoinMock.json new file mode 100644 index 0000000..e6b8ca4 --- /dev/null +++ b/deployments/optimism-sepolia/FakeStablecoinMock.json @@ -0,0 +1,559 @@ +{ + "address": "0x7Ec44363Fdaa7EEC9B49a858220Ff543Bcd43ac7", + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "allowance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientAllowance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "ERC20InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC20InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "ERC20InvalidSender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "ERC20InvalidSpender", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "transactionHash": "0x3fae44b0d59ebffdfe79896cc2fc32e62bcbafeaf1d1c5c92fe91a3f5b7327b6", + "receipt": { + "to": null, + "from": "0x8583894d0e57e42abb83039537f314490038efa0", + "contractAddress": "0x7Ec44363Fdaa7EEC9B49a858220Ff543Bcd43ac7", + "transactionIndex": 2, + "gasUsed": "526781", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xdfbe48aace1a6a1258e186bd6868ab8ad66276dc149b313312b0fda05d08d803", + "transactionHash": "0x3fae44b0d59ebffdfe79896cc2fc32e62bcbafeaf1d1c5c92fe91a3f5b7327b6", + "logs": [], + "blockNumber": 46864831, + "cumulativeGasUsed": "593999", + "status": 1, + "byzantium": true + }, + "args": [], + "numDeployments": 1, + "solcInputHash": "31caad50e704c80e4a3252d0a262b59d", + "metadata": "{\"compiler\":{\"version\":\"0.8.22+commit.4fc1097e\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"allowance\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"needed\",\"type\":\"uint256\"}],\"name\":\"ERC20InsufficientAllowance\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"needed\",\"type\":\"uint256\"}],\"name\":\"ERC20InsufficientBalance\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"approver\",\"type\":\"address\"}],\"name\":\"ERC20InvalidApprover\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"}],\"name\":\"ERC20InvalidReceiver\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"ERC20InvalidSender\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"ERC20InvalidSpender\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"mint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"details\":\"The engine's token screening resolves a subject's underlying token through `token()`, reads `symbol()`/`decimals()`, and compares the address against the chain's canonical issuer (see `CANONICAL_STABLECOINS` in worker/assess/providers/token.ts). Claiming a watched symbol from a non-canonical address is exactly the pattern `fake_stablecoin_suspect` exists to catch \\u2014 so this contract asserts the symbol and nothing else. Deploy only to testnets.\",\"errors\":{\"ERC20InsufficientAllowance(address,uint256,uint256)\":[{\"details\":\"Indicates a failure with the `spender`\\u2019s `allowance`. Used in transfers.\",\"params\":{\"allowance\":\"Amount of tokens a `spender` is allowed to operate with.\",\"needed\":\"Minimum amount required to perform a transfer.\",\"spender\":\"Address that may be allowed to operate on tokens without being their owner.\"}}],\"ERC20InsufficientBalance(address,uint256,uint256)\":[{\"details\":\"Indicates an error related to the current `balance` of a `sender`. Used in transfers.\",\"params\":{\"balance\":\"Current balance for the interacting account.\",\"needed\":\"Minimum amount required to perform a transfer.\",\"sender\":\"Address whose tokens are being transferred.\"}}],\"ERC20InvalidApprover(address)\":[{\"details\":\"Indicates a failure with the `approver` of a token to be approved. Used in approvals.\",\"params\":{\"approver\":\"Address initiating an approval operation.\"}}],\"ERC20InvalidReceiver(address)\":[{\"details\":\"Indicates a failure with the token `receiver`. Used in transfers.\",\"params\":{\"receiver\":\"Address to which tokens are being transferred.\"}}],\"ERC20InvalidSender(address)\":[{\"details\":\"Indicates a failure with the token `sender`. Used in transfers.\",\"params\":{\"sender\":\"Address whose tokens are being transferred.\"}}],\"ERC20InvalidSpender(address)\":[{\"details\":\"Indicates a failure with the `spender` to be approved. Used in approvals.\",\"params\":{\"spender\":\"Address that may be allowed to operate on tokens without being their owner.\"}}]},\"events\":{\"Approval(address,address,uint256)\":{\"details\":\"Emitted when the allowance of a `spender` for an `owner` is set by a call to {approve}. `value` is the new allowance.\"},\"Transfer(address,address,uint256)\":{\"details\":\"Emitted when `value` tokens are moved from one account (`from`) to another (`to`). Note that `value` may be zero.\"}},\"kind\":\"dev\",\"methods\":{\"allowance(address,address)\":{\"details\":\"Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` through {transferFrom}. This is zero by default. This value changes when {approve} or {transferFrom} are called.\"},\"approve(address,uint256)\":{\"details\":\"See {IERC20-approve}. NOTE: If `value` is the maximum `uint256`, the allowance is not updated on `transferFrom`. This is semantically equivalent to an infinite approval. Requirements: - `spender` cannot be the zero address.\"},\"balanceOf(address)\":{\"details\":\"Returns the value of tokens owned by `account`.\"},\"decimals()\":{\"details\":\"Six, like the real thing: the check is about the address, and matching the decimals keeps the decoy from being dismissed on a detail the engine does not rely on.\"},\"name()\":{\"details\":\"Returns the name of the token.\"},\"symbol()\":{\"details\":\"Returns the symbol of the token, usually a shorter version of the name.\"},\"token()\":{\"details\":\"This is what makes the engine treat the address as a token rather than a plain OApp: `resolveToken` calls `token()` and screens whatever address comes back.\"},\"totalSupply()\":{\"details\":\"Returns the value of tokens in existence.\"},\"transfer(address,uint256)\":{\"details\":\"See {IERC20-transfer}. Requirements: - `to` cannot be the zero address. - the caller must have a balance of at least `value`.\"},\"transferFrom(address,address,uint256)\":{\"details\":\"See {IERC20-transferFrom}. Skips emitting an {Approval} event indicating an allowance update. This is not required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve]. NOTE: Does not update the allowance if the current allowance is the maximum `uint256`. Requirements: - `from` and `to` cannot be the zero address. - `from` must have a balance of at least `value`. - the caller must have allowance for ``from``'s tokens of at least `value`.\"}},\"title\":\"FakeStablecoinMock\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"mint(address,uint256)\":{\"notice\":\"Open mint, testnet only \\u2014 a decoy with no supply is harder to look at in an explorer.\"},\"token()\":{\"notice\":\"Reports itself as its own underlying token.\"}},\"notice\":\"A testnet decoy that claims to be USDC, for exercising the risk engine's impersonation check. It is NOT a stablecoin and holds no value.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/mocks/FakeStablecoinMock.sol\":\"FakeStablecoinMock\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/interfaces/draft-IERC6093.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.5.0) (interfaces/draft-IERC6093.sol)\\n\\npragma solidity >=0.8.4;\\n\\n/**\\n * @dev Standard ERC-20 Errors\\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-20 tokens.\\n */\\ninterface IERC20Errors {\\n /**\\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n * @param balance Current balance for the interacting account.\\n * @param needed Minimum amount required to perform a transfer.\\n */\\n error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);\\n\\n /**\\n * @dev Indicates a failure with the token `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n */\\n error ERC20InvalidSender(address sender);\\n\\n /**\\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\\n * @param receiver Address to which tokens are being transferred.\\n */\\n error ERC20InvalidReceiver(address receiver);\\n\\n /**\\n * @dev Indicates a failure with the `spender`\\u2019s `allowance`. Used in transfers.\\n * @param spender Address that may be allowed to operate on tokens without being their owner.\\n * @param allowance Amount of tokens a `spender` is allowed to operate with.\\n * @param needed Minimum amount required to perform a transfer.\\n */\\n error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);\\n\\n /**\\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\\n * @param approver Address initiating an approval operation.\\n */\\n error ERC20InvalidApprover(address approver);\\n\\n /**\\n * @dev Indicates a failure with the `spender` to be approved. Used in approvals.\\n * @param spender Address that may be allowed to operate on tokens without being their owner.\\n */\\n error ERC20InvalidSpender(address spender);\\n}\\n\\n/**\\n * @dev Standard ERC-721 Errors\\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-721 tokens.\\n */\\ninterface IERC721Errors {\\n /**\\n * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-721.\\n * Used in balance queries.\\n * @param owner Address of the current owner of a token.\\n */\\n error ERC721InvalidOwner(address owner);\\n\\n /**\\n * @dev Indicates a `tokenId` whose `owner` is the zero address.\\n * @param tokenId Identifier number of a token.\\n */\\n error ERC721NonexistentToken(uint256 tokenId);\\n\\n /**\\n * @dev Indicates an error related to the ownership over a particular token. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n * @param tokenId Identifier number of a token.\\n * @param owner Address of the current owner of a token.\\n */\\n error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);\\n\\n /**\\n * @dev Indicates a failure with the token `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n */\\n error ERC721InvalidSender(address sender);\\n\\n /**\\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\\n * @param receiver Address to which tokens are being transferred.\\n */\\n error ERC721InvalidReceiver(address receiver);\\n\\n /**\\n * @dev Indicates a failure with the `operator`\\u2019s approval. Used in transfers.\\n * @param operator Address that may be allowed to operate on tokens without being their owner.\\n * @param tokenId Identifier number of a token.\\n */\\n error ERC721InsufficientApproval(address operator, uint256 tokenId);\\n\\n /**\\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\\n * @param approver Address initiating an approval operation.\\n */\\n error ERC721InvalidApprover(address approver);\\n\\n /**\\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\\n * @param operator Address that may be allowed to operate on tokens without being their owner.\\n */\\n error ERC721InvalidOperator(address operator);\\n}\\n\\n/**\\n * @dev Standard ERC-1155 Errors\\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-1155 tokens.\\n */\\ninterface IERC1155Errors {\\n /**\\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n * @param balance Current balance for the interacting account.\\n * @param needed Minimum amount required to perform a transfer.\\n * @param tokenId Identifier number of a token.\\n */\\n error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);\\n\\n /**\\n * @dev Indicates a failure with the token `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n */\\n error ERC1155InvalidSender(address sender);\\n\\n /**\\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\\n * @param receiver Address to which tokens are being transferred.\\n */\\n error ERC1155InvalidReceiver(address receiver);\\n\\n /**\\n * @dev Indicates a failure with the `operator`\\u2019s approval. Used in transfers.\\n * @param operator Address that may be allowed to operate on tokens without being their owner.\\n * @param owner Address of the current owner of a token.\\n */\\n error ERC1155MissingApprovalForAll(address operator, address owner);\\n\\n /**\\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\\n * @param approver Address initiating an approval operation.\\n */\\n error ERC1155InvalidApprover(address approver);\\n\\n /**\\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\\n * @param operator Address that may be allowed to operate on tokens without being their owner.\\n */\\n error ERC1155InvalidOperator(address operator);\\n\\n /**\\n * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.\\n * Used in batch transfers.\\n * @param idsLength Length of the array of token identifiers\\n * @param valuesLength Length of the array of token amounts\\n */\\n error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);\\n}\\n\",\"keccak256\":\"0x1b88b3fb3d85ba5496d7d5f396f83ee1fddcdd6762059ff65992655b67920998\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/ERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.5.0) (token/ERC20/ERC20.sol)\\n\\npragma solidity ^0.8.20;\\n\\nimport {IERC20} from \\\"./IERC20.sol\\\";\\nimport {IERC20Metadata} from \\\"./extensions/IERC20Metadata.sol\\\";\\nimport {Context} from \\\"../../utils/Context.sol\\\";\\nimport {IERC20Errors} from \\\"../../interfaces/draft-IERC6093.sol\\\";\\n\\n/**\\n * @dev Implementation of the {IERC20} interface.\\n *\\n * This implementation is agnostic to the way tokens are created. This means\\n * that a supply mechanism has to be added in a derived contract using {_mint}.\\n *\\n * TIP: For a detailed writeup see our guide\\n * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How\\n * to implement supply mechanisms].\\n *\\n * The default value of {decimals} is 18. To change this, you should override\\n * this function so it returns a different value.\\n *\\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\\n * instead returning `false` on failure. This behavior is nonetheless\\n * conventional and does not conflict with the expectations of ERC-20\\n * applications.\\n */\\nabstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {\\n mapping(address account => uint256) private _balances;\\n\\n mapping(address account => mapping(address spender => uint256)) private _allowances;\\n\\n uint256 private _totalSupply;\\n\\n string private _name;\\n string private _symbol;\\n\\n /**\\n * @dev Sets the values for {name} and {symbol}.\\n *\\n * Both values are immutable: they can only be set once during construction.\\n */\\n constructor(string memory name_, string memory symbol_) {\\n _name = name_;\\n _symbol = symbol_;\\n }\\n\\n /**\\n * @dev Returns the name of the token.\\n */\\n function name() public view virtual returns (string memory) {\\n return _name;\\n }\\n\\n /**\\n * @dev Returns the symbol of the token, usually a shorter version of the\\n * name.\\n */\\n function symbol() public view virtual returns (string memory) {\\n return _symbol;\\n }\\n\\n /**\\n * @dev Returns the number of decimals used to get its user representation.\\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\\n *\\n * Tokens usually opt for a value of 18, imitating the relationship between\\n * Ether and Wei. This is the default value returned by this function, unless\\n * it's overridden.\\n *\\n * NOTE: This information is only used for _display_ purposes: it in\\n * no way affects any of the arithmetic of the contract, including\\n * {IERC20-balanceOf} and {IERC20-transfer}.\\n */\\n function decimals() public view virtual returns (uint8) {\\n return 18;\\n }\\n\\n /// @inheritdoc IERC20\\n function totalSupply() public view virtual returns (uint256) {\\n return _totalSupply;\\n }\\n\\n /// @inheritdoc IERC20\\n function balanceOf(address account) public view virtual returns (uint256) {\\n return _balances[account];\\n }\\n\\n /**\\n * @dev See {IERC20-transfer}.\\n *\\n * Requirements:\\n *\\n * - `to` cannot be the zero address.\\n * - the caller must have a balance of at least `value`.\\n */\\n function transfer(address to, uint256 value) public virtual returns (bool) {\\n address owner = _msgSender();\\n _transfer(owner, to, value);\\n return true;\\n }\\n\\n /// @inheritdoc IERC20\\n function allowance(address owner, address spender) public view virtual returns (uint256) {\\n return _allowances[owner][spender];\\n }\\n\\n /**\\n * @dev See {IERC20-approve}.\\n *\\n * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on\\n * `transferFrom`. This is semantically equivalent to an infinite approval.\\n *\\n * Requirements:\\n *\\n * - `spender` cannot be the zero address.\\n */\\n function approve(address spender, uint256 value) public virtual returns (bool) {\\n address owner = _msgSender();\\n _approve(owner, spender, value);\\n return true;\\n }\\n\\n /**\\n * @dev See {IERC20-transferFrom}.\\n *\\n * Skips emitting an {Approval} event indicating an allowance update. This is not\\n * required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].\\n *\\n * NOTE: Does not update the allowance if the current allowance\\n * is the maximum `uint256`.\\n *\\n * Requirements:\\n *\\n * - `from` and `to` cannot be the zero address.\\n * - `from` must have a balance of at least `value`.\\n * - the caller must have allowance for ``from``'s tokens of at least\\n * `value`.\\n */\\n function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {\\n address spender = _msgSender();\\n _spendAllowance(from, spender, value);\\n _transfer(from, to, value);\\n return true;\\n }\\n\\n /**\\n * @dev Moves a `value` amount of tokens from `from` to `to`.\\n *\\n * This internal function is equivalent to {transfer}, and can be used to\\n * e.g. implement automatic token fees, slashing mechanisms, etc.\\n *\\n * Emits a {Transfer} event.\\n *\\n * NOTE: This function is not virtual, {_update} should be overridden instead.\\n */\\n function _transfer(address from, address to, uint256 value) internal {\\n if (from == address(0)) {\\n revert ERC20InvalidSender(address(0));\\n }\\n if (to == address(0)) {\\n revert ERC20InvalidReceiver(address(0));\\n }\\n _update(from, to, value);\\n }\\n\\n /**\\n * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`\\n * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding\\n * this function.\\n *\\n * Emits a {Transfer} event.\\n */\\n function _update(address from, address to, uint256 value) internal virtual {\\n if (from == address(0)) {\\n // Overflow check required: The rest of the code assumes that totalSupply never overflows\\n _totalSupply += value;\\n } else {\\n uint256 fromBalance = _balances[from];\\n if (fromBalance < value) {\\n revert ERC20InsufficientBalance(from, fromBalance, value);\\n }\\n unchecked {\\n // Overflow not possible: value <= fromBalance <= totalSupply.\\n _balances[from] = fromBalance - value;\\n }\\n }\\n\\n if (to == address(0)) {\\n unchecked {\\n // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.\\n _totalSupply -= value;\\n }\\n } else {\\n unchecked {\\n // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.\\n _balances[to] += value;\\n }\\n }\\n\\n emit Transfer(from, to, value);\\n }\\n\\n /**\\n * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).\\n * Relies on the `_update` mechanism\\n *\\n * Emits a {Transfer} event with `from` set to the zero address.\\n *\\n * NOTE: This function is not virtual, {_update} should be overridden instead.\\n */\\n function _mint(address account, uint256 value) internal {\\n if (account == address(0)) {\\n revert ERC20InvalidReceiver(address(0));\\n }\\n _update(address(0), account, value);\\n }\\n\\n /**\\n * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.\\n * Relies on the `_update` mechanism.\\n *\\n * Emits a {Transfer} event with `to` set to the zero address.\\n *\\n * NOTE: This function is not virtual, {_update} should be overridden instead\\n */\\n function _burn(address account, uint256 value) internal {\\n if (account == address(0)) {\\n revert ERC20InvalidSender(address(0));\\n }\\n _update(account, address(0), value);\\n }\\n\\n /**\\n * @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.\\n *\\n * This internal function is equivalent to `approve`, and can be used to\\n * e.g. set automatic allowances for certain subsystems, etc.\\n *\\n * Emits an {Approval} event.\\n *\\n * Requirements:\\n *\\n * - `owner` cannot be the zero address.\\n * - `spender` cannot be the zero address.\\n *\\n * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.\\n */\\n function _approve(address owner, address spender, uint256 value) internal {\\n _approve(owner, spender, value, true);\\n }\\n\\n /**\\n * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.\\n *\\n * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by\\n * `_spendAllowance` during the `transferFrom` operation sets the flag to false. This saves gas by not emitting any\\n * `Approval` event during `transferFrom` operations.\\n *\\n * Anyone who wishes to continue emitting `Approval` events on the `transferFrom` operation can force the flag to\\n * true using the following override:\\n *\\n * ```solidity\\n * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {\\n * super._approve(owner, spender, value, true);\\n * }\\n * ```\\n *\\n * Requirements are the same as {_approve}.\\n */\\n function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {\\n if (owner == address(0)) {\\n revert ERC20InvalidApprover(address(0));\\n }\\n if (spender == address(0)) {\\n revert ERC20InvalidSpender(address(0));\\n }\\n _allowances[owner][spender] = value;\\n if (emitEvent) {\\n emit Approval(owner, spender, value);\\n }\\n }\\n\\n /**\\n * @dev Updates `owner`'s allowance for `spender` based on spent `value`.\\n *\\n * Does not update the allowance value in case of infinite allowance.\\n * Revert if not enough allowance is available.\\n *\\n * Does not emit an {Approval} event.\\n */\\n function _spendAllowance(address owner, address spender, uint256 value) internal virtual {\\n uint256 currentAllowance = allowance(owner, spender);\\n if (currentAllowance < type(uint256).max) {\\n if (currentAllowance < value) {\\n revert ERC20InsufficientAllowance(spender, currentAllowance, value);\\n }\\n unchecked {\\n _approve(owner, spender, currentAllowance - value, false);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x669464167428061ee0f8618b73b3ee90aff8405683e7ddde8cd77dadaa1afe29\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)\\n\\npragma solidity >=0.4.16;\\n\\n/**\\n * @dev Interface of the ERC-20 standard as defined in the ERC.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n\\n /**\\n * @dev Returns the value of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the value of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from the caller's account to `to`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address to, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\\n * caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from `from` to `to` using the\\n * allowance mechanism. `value` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(address from, address to, uint256 value) external returns (bool);\\n}\\n\",\"keccak256\":\"0x74ed01eb66b923d0d0cfe3be84604ac04b76482a55f9dd655e1ef4d367f95bc2\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/extensions/IERC20Metadata.sol)\\n\\npragma solidity >=0.6.2;\\n\\nimport {IERC20} from \\\"../IERC20.sol\\\";\\n\\n/**\\n * @dev Interface for the optional metadata functions from the ERC-20 standard.\\n */\\ninterface IERC20Metadata is IERC20 {\\n /**\\n * @dev Returns the name of the token.\\n */\\n function name() external view returns (string memory);\\n\\n /**\\n * @dev Returns the symbol of the token.\\n */\\n function symbol() external view returns (string memory);\\n\\n /**\\n * @dev Returns the decimals places of the token.\\n */\\n function decimals() external view returns (uint8);\\n}\\n\",\"keccak256\":\"0xd6fa4088198f04eef10c5bce8a2f4d60554b7ec4b987f684393c01bf79b94d9f\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Context.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\\n\\npragma solidity ^0.8.20;\\n\\n/**\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract Context {\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n\\n function _contextSuffixLength() internal view virtual returns (uint256) {\\n return 0;\\n }\\n}\\n\",\"keccak256\":\"0x493033a8d1b176a037b2cc6a04dad01a5c157722049bbecf632ca876224dd4b2\",\"license\":\"MIT\"},\"contracts/mocks/FakeStablecoinMock.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.22;\\n\\nimport { ERC20 } from \\\"@openzeppelin/contracts/token/ERC20/ERC20.sol\\\";\\n\\n/// @title FakeStablecoinMock\\n/// @notice A testnet decoy that claims to be USDC, for exercising the risk engine's\\n/// impersonation check. It is NOT a stablecoin and holds no value.\\n/// @dev The engine's token screening resolves a subject's underlying token through `token()`,\\n/// reads `symbol()`/`decimals()`, and compares the address against the chain's canonical\\n/// issuer (see `CANONICAL_STABLECOINS` in worker/assess/providers/token.ts). Claiming a\\n/// watched symbol from a non-canonical address is exactly the pattern\\n/// `fake_stablecoin_suspect` exists to catch \\u2014 so this contract asserts the symbol and\\n/// nothing else. Deploy only to testnets.\\ncontract FakeStablecoinMock is ERC20 {\\n constructor() ERC20(\\\"USD Coin\\\", \\\"USDC\\\") {}\\n\\n /// @dev Six, like the real thing: the check is about the address, and matching the decimals\\n /// keeps the decoy from being dismissed on a detail the engine does not rely on.\\n function decimals() public pure override returns (uint8) {\\n return 6;\\n }\\n\\n /// @notice Reports itself as its own underlying token.\\n /// @dev This is what makes the engine treat the address as a token rather than a plain OApp:\\n /// `resolveToken` calls `token()` and screens whatever address comes back.\\n function token() external view returns (address) {\\n return address(this);\\n }\\n\\n /// @notice Open mint, testnet only \\u2014 a decoy with no supply is harder to look at in an explorer.\\n function mint(address _to, uint256 _amount) external {\\n _mint(_to, _amount);\\n }\\n}\\n\",\"keccak256\":\"0xb593eb06f63d8e10e8913c6b398d5e26f3185d91ae8f7042147bb8cf6f963287\",\"license\":\"MIT\"}},\"version\":1}", + "bytecode": "0x608060405234801561001057600080fd5b50604051806040016040528060088152602001672aa9a21021b7b4b760c11b815250604051806040016040528060048152602001635553444360e01b815250816003908161005e9190610114565b50600461006b8282610114565b5050506101d3565b634e487b7160e01b600052604160045260246000fd5b600181811c9082168061009d57607f821691505b6020821081036100bd57634e487b7160e01b600052602260045260246000fd5b50919050565b601f82111561010f576000816000526020600020601f850160051c810160208610156100ec5750805b601f850160051c820191505b8181101561010b578281556001016100f8565b5050505b505050565b81516001600160401b0381111561012d5761012d610073565b6101418161013b8454610089565b846100c3565b602080601f831160018114610176576000841561015e5750858301515b600019600386901b1c1916600185901b17855561010b565b600085815260208120601f198616915b828110156101a557888601518255948401946001909101908401610186565b50858210156101c35787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b61079f806101e26000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c806340c10f191161007157806340c10f191461012357806370a082311461013857806395d89b4114610161578063a9059cbb14610169578063dd62ed3e1461017c578063fc0c546a146101b557600080fd5b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100ef57806323b872dd14610101578063313ce56714610114575b600080fd5b6100b66101c3565b6040516100c391906105e8565b60405180910390f35b6100df6100da366004610653565b610255565b60405190151581526020016100c3565b6002545b6040519081526020016100c3565b6100df61010f36600461067d565b61026f565b604051600681526020016100c3565b610136610131366004610653565b610293565b005b6100f36101463660046106b9565b6001600160a01b031660009081526020819052604090205490565b6100b66102a1565b6100df610177366004610653565b6102b0565b6100f361018a3660046106db565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6040513081526020016100c3565b6060600380546101d29061070e565b80601f01602080910402602001604051908101604052809291908181526020018280546101fe9061070e565b801561024b5780601f106102205761010080835404028352916020019161024b565b820191906000526020600020905b81548152906001019060200180831161022e57829003601f168201915b5050505050905090565b6000336102638185856102be565b60019150505b92915050565b60003361027d8582856102d0565b610288858585610354565b506001949350505050565b61029d82826103b3565b5050565b6060600480546101d29061070e565b600033610263818585610354565b6102cb83838360016103e9565b505050565b6001600160a01b0383811660009081526001602090815260408083209386168352929052205460001981101561034e578181101561033f57604051637dc7a0d960e11b81526001600160a01b038416600482015260248101829052604481018390526064015b60405180910390fd5b61034e848484840360006103e9565b50505050565b6001600160a01b03831661037e57604051634b637e8f60e11b815260006004820152602401610336565b6001600160a01b0382166103a85760405163ec442f0560e01b815260006004820152602401610336565b6102cb8383836104be565b6001600160a01b0382166103dd5760405163ec442f0560e01b815260006004820152602401610336565b61029d600083836104be565b6001600160a01b0384166104135760405163e602df0560e01b815260006004820152602401610336565b6001600160a01b03831661043d57604051634a1406b160e11b815260006004820152602401610336565b6001600160a01b038085166000908152600160209081526040808320938716835292905220829055801561034e57826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040516104b091815260200190565b60405180910390a350505050565b6001600160a01b0383166104e95780600260008282546104de9190610748565b9091555061055b9050565b6001600160a01b0383166000908152602081905260409020548181101561053c5760405163391434e360e21b81526001600160a01b03851660048201526024810182905260448101839052606401610336565b6001600160a01b03841660009081526020819052604090209082900390555b6001600160a01b03821661057757600280548290039055610596565b6001600160a01b03821660009081526020819052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516105db91815260200190565b60405180910390a3505050565b60006020808352835180602085015260005b81811015610616578581018301518582016040015282016105fa565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b038116811461064e57600080fd5b919050565b6000806040838503121561066657600080fd5b61066f83610637565b946020939093013593505050565b60008060006060848603121561069257600080fd5b61069b84610637565b92506106a960208501610637565b9150604084013590509250925092565b6000602082840312156106cb57600080fd5b6106d482610637565b9392505050565b600080604083850312156106ee57600080fd5b6106f783610637565b915061070560208401610637565b90509250929050565b600181811c9082168061072257607f821691505b60208210810361074257634e487b7160e01b600052602260045260246000fd5b50919050565b8082018082111561026957634e487b7160e01b600052601160045260246000fdfea2646970667358221220c4840d9c77eca3ef0ead0a97a130e6b366b42fde6b4af36b1a94a13b78cc069664736f6c63430008160033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100a95760003560e01c806340c10f191161007157806340c10f191461012357806370a082311461013857806395d89b4114610161578063a9059cbb14610169578063dd62ed3e1461017c578063fc0c546a146101b557600080fd5b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100ef57806323b872dd14610101578063313ce56714610114575b600080fd5b6100b66101c3565b6040516100c391906105e8565b60405180910390f35b6100df6100da366004610653565b610255565b60405190151581526020016100c3565b6002545b6040519081526020016100c3565b6100df61010f36600461067d565b61026f565b604051600681526020016100c3565b610136610131366004610653565b610293565b005b6100f36101463660046106b9565b6001600160a01b031660009081526020819052604090205490565b6100b66102a1565b6100df610177366004610653565b6102b0565b6100f361018a3660046106db565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6040513081526020016100c3565b6060600380546101d29061070e565b80601f01602080910402602001604051908101604052809291908181526020018280546101fe9061070e565b801561024b5780601f106102205761010080835404028352916020019161024b565b820191906000526020600020905b81548152906001019060200180831161022e57829003601f168201915b5050505050905090565b6000336102638185856102be565b60019150505b92915050565b60003361027d8582856102d0565b610288858585610354565b506001949350505050565b61029d82826103b3565b5050565b6060600480546101d29061070e565b600033610263818585610354565b6102cb83838360016103e9565b505050565b6001600160a01b0383811660009081526001602090815260408083209386168352929052205460001981101561034e578181101561033f57604051637dc7a0d960e11b81526001600160a01b038416600482015260248101829052604481018390526064015b60405180910390fd5b61034e848484840360006103e9565b50505050565b6001600160a01b03831661037e57604051634b637e8f60e11b815260006004820152602401610336565b6001600160a01b0382166103a85760405163ec442f0560e01b815260006004820152602401610336565b6102cb8383836104be565b6001600160a01b0382166103dd5760405163ec442f0560e01b815260006004820152602401610336565b61029d600083836104be565b6001600160a01b0384166104135760405163e602df0560e01b815260006004820152602401610336565b6001600160a01b03831661043d57604051634a1406b160e11b815260006004820152602401610336565b6001600160a01b038085166000908152600160209081526040808320938716835292905220829055801561034e57826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040516104b091815260200190565b60405180910390a350505050565b6001600160a01b0383166104e95780600260008282546104de9190610748565b9091555061055b9050565b6001600160a01b0383166000908152602081905260409020548181101561053c5760405163391434e360e21b81526001600160a01b03851660048201526024810182905260448101839052606401610336565b6001600160a01b03841660009081526020819052604090209082900390555b6001600160a01b03821661057757600280548290039055610596565b6001600160a01b03821660009081526020819052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516105db91815260200190565b60405180910390a3505050565b60006020808352835180602085015260005b81811015610616578581018301518582016040015282016105fa565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b038116811461064e57600080fd5b919050565b6000806040838503121561066657600080fd5b61066f83610637565b946020939093013593505050565b60008060006060848603121561069257600080fd5b61069b84610637565b92506106a960208501610637565b9150604084013590509250925092565b6000602082840312156106cb57600080fd5b6106d482610637565b9392505050565b600080604083850312156106ee57600080fd5b6106f783610637565b915061070560208401610637565b90509250929050565b600181811c9082168061072257607f821691505b60208210810361074257634e487b7160e01b600052602260045260246000fd5b50919050565b8082018082111561026957634e487b7160e01b600052601160045260246000fdfea2646970667358221220c4840d9c77eca3ef0ead0a97a130e6b366b42fde6b4af36b1a94a13b78cc069664736f6c63430008160033", + "devdoc": { + "details": "The engine's token screening resolves a subject's underlying token through `token()`, reads `symbol()`/`decimals()`, and compares the address against the chain's canonical issuer (see `CANONICAL_STABLECOINS` in worker/assess/providers/token.ts). Claiming a watched symbol from a non-canonical address is exactly the pattern `fake_stablecoin_suspect` exists to catch — so this contract asserts the symbol and nothing else. Deploy only to testnets.", + "errors": { + "ERC20InsufficientAllowance(address,uint256,uint256)": [ + { + "details": "Indicates a failure with the `spender`’s `allowance`. Used in transfers.", + "params": { + "allowance": "Amount of tokens a `spender` is allowed to operate with.", + "needed": "Minimum amount required to perform a transfer.", + "spender": "Address that may be allowed to operate on tokens without being their owner." + } + } + ], + "ERC20InsufficientBalance(address,uint256,uint256)": [ + { + "details": "Indicates an error related to the current `balance` of a `sender`. Used in transfers.", + "params": { + "balance": "Current balance for the interacting account.", + "needed": "Minimum amount required to perform a transfer.", + "sender": "Address whose tokens are being transferred." + } + } + ], + "ERC20InvalidApprover(address)": [ + { + "details": "Indicates a failure with the `approver` of a token to be approved. Used in approvals.", + "params": { + "approver": "Address initiating an approval operation." + } + } + ], + "ERC20InvalidReceiver(address)": [ + { + "details": "Indicates a failure with the token `receiver`. Used in transfers.", + "params": { + "receiver": "Address to which tokens are being transferred." + } + } + ], + "ERC20InvalidSender(address)": [ + { + "details": "Indicates a failure with the token `sender`. Used in transfers.", + "params": { + "sender": "Address whose tokens are being transferred." + } + } + ], + "ERC20InvalidSpender(address)": [ + { + "details": "Indicates a failure with the `spender` to be approved. Used in approvals.", + "params": { + "spender": "Address that may be allowed to operate on tokens without being their owner." + } + } + ] + }, + "events": { + "Approval(address,address,uint256)": { + "details": "Emitted when the allowance of a `spender` for an `owner` is set by a call to {approve}. `value` is the new allowance." + }, + "Transfer(address,address,uint256)": { + "details": "Emitted when `value` tokens are moved from one account (`from`) to another (`to`). Note that `value` may be zero." + } + }, + "kind": "dev", + "methods": { + "allowance(address,address)": { + "details": "Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` through {transferFrom}. This is zero by default. This value changes when {approve} or {transferFrom} are called." + }, + "approve(address,uint256)": { + "details": "See {IERC20-approve}. NOTE: If `value` is the maximum `uint256`, the allowance is not updated on `transferFrom`. This is semantically equivalent to an infinite approval. Requirements: - `spender` cannot be the zero address." + }, + "balanceOf(address)": { + "details": "Returns the value of tokens owned by `account`." + }, + "decimals()": { + "details": "Six, like the real thing: the check is about the address, and matching the decimals keeps the decoy from being dismissed on a detail the engine does not rely on." + }, + "name()": { + "details": "Returns the name of the token." + }, + "symbol()": { + "details": "Returns the symbol of the token, usually a shorter version of the name." + }, + "token()": { + "details": "This is what makes the engine treat the address as a token rather than a plain OApp: `resolveToken` calls `token()` and screens whatever address comes back." + }, + "totalSupply()": { + "details": "Returns the value of tokens in existence." + }, + "transfer(address,uint256)": { + "details": "See {IERC20-transfer}. Requirements: - `to` cannot be the zero address. - the caller must have a balance of at least `value`." + }, + "transferFrom(address,address,uint256)": { + "details": "See {IERC20-transferFrom}. Skips emitting an {Approval} event indicating an allowance update. This is not required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve]. NOTE: Does not update the allowance if the current allowance is the maximum `uint256`. Requirements: - `from` and `to` cannot be the zero address. - `from` must have a balance of at least `value`. - the caller must have allowance for ``from``'s tokens of at least `value`." + } + }, + "title": "FakeStablecoinMock", + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "mint(address,uint256)": { + "notice": "Open mint, testnet only — a decoy with no supply is harder to look at in an explorer." + }, + "token()": { + "notice": "Reports itself as its own underlying token." + } + }, + "notice": "A testnet decoy that claims to be USDC, for exercising the risk engine's impersonation check. It is NOT a stablecoin and holds no value.", + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 159, + "contract": "contracts/mocks/FakeStablecoinMock.sol:FakeStablecoinMock", + "label": "_balances", + "offset": 0, + "slot": "0", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 165, + "contract": "contracts/mocks/FakeStablecoinMock.sol:FakeStablecoinMock", + "label": "_allowances", + "offset": 0, + "slot": "1", + "type": "t_mapping(t_address,t_mapping(t_address,t_uint256))" + }, + { + "astId": 167, + "contract": "contracts/mocks/FakeStablecoinMock.sol:FakeStablecoinMock", + "label": "_totalSupply", + "offset": 0, + "slot": "2", + "type": "t_uint256" + }, + { + "astId": 169, + "contract": "contracts/mocks/FakeStablecoinMock.sol:FakeStablecoinMock", + "label": "_name", + "offset": 0, + "slot": "3", + "type": "t_string_storage" + }, + { + "astId": 171, + "contract": "contracts/mocks/FakeStablecoinMock.sol:FakeStablecoinMock", + "label": "_symbol", + "offset": 0, + "slot": "4", + "type": "t_string_storage" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32", + "value": "t_mapping(t_address,t_uint256)" + }, + "t_mapping(t_address,t_uint256)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_string_storage": { + "encoding": "bytes", + "label": "string", + "numberOfBytes": "32" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + } + } + } +} \ No newline at end of file diff --git a/deployments/optimism-sepolia/FakeUsdcOFT.json b/deployments/optimism-sepolia/FakeUsdcOFT.json new file mode 100644 index 0000000..14d3804 --- /dev/null +++ b/deployments/optimism-sepolia/FakeUsdcOFT.json @@ -0,0 +1,2191 @@ +{ + "address": "0xf970027c806420a0222F5b72b097d3fDeC81b228", + "abi": [ + { + "inputs": [ + { + "internalType": "string", + "name": "_name", + "type": "string" + }, + { + "internalType": "string", + "name": "_symbol", + "type": "string" + }, + { + "internalType": "address", + "name": "_lzEndpoint", + "type": "address" + }, + { + "internalType": "address", + "name": "_delegate", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountSD", + "type": "uint256" + } + ], + "name": "AmountSDOverflowed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "allowance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientAllowance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "ERC20InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC20InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "ERC20InvalidSender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "ERC20InvalidSpender", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidDelegate", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidEndpointCall", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidLocalDecimals", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "options", + "type": "bytes" + } + ], + "name": "InvalidOptions", + "type": "error" + }, + { + "inputs": [], + "name": "LzTokenUnavailable", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "eid", + "type": "uint32" + } + ], + "name": "NoPeer", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "msgValue", + "type": "uint256" + } + ], + "name": "NotEnoughNative", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "OnlyEndpoint", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "eid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + } + ], + "name": "OnlyPeer", + "type": "error" + }, + { + "inputs": [], + "name": "OnlySelf", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "result", + "type": "bytes" + } + ], + "name": "SimulationResult", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountLD", + "type": "uint256" + } + ], + "name": "SlippageExceeded", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "eid", + "type": "uint32" + }, + { + "internalType": "uint16", + "name": "msgType", + "type": "uint16" + }, + { + "internalType": "bytes", + "name": "options", + "type": "bytes" + } + ], + "indexed": false, + "internalType": "struct EnforcedOptionParam[]", + "name": "_enforcedOptions", + "type": "tuple[]" + } + ], + "name": "EnforcedOptionSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "inspector", + "type": "address" + } + ], + "name": "MsgInspectorSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "guid", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "srcEid", + "type": "uint32" + }, + { + "indexed": true, + "internalType": "address", + "name": "toAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountReceivedLD", + "type": "uint256" + } + ], + "name": "OFTReceived", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "guid", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "dstEid", + "type": "uint32" + }, + { + "indexed": true, + "internalType": "address", + "name": "fromAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountSentLD", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountReceivedLD", + "type": "uint256" + } + ], + "name": "OFTSent", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint32", + "name": "eid", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "peer", + "type": "bytes32" + } + ], + "name": "PeerSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "preCrimeAddress", + "type": "address" + } + ], + "name": "PreCrimeSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "SEND", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SEND_AND_CALL", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "srcEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "internalType": "struct Origin", + "name": "origin", + "type": "tuple" + } + ], + "name": "allowInitializePath", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "approvalRequired", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_eid", + "type": "uint32" + }, + { + "internalType": "uint16", + "name": "_msgType", + "type": "uint16" + }, + { + "internalType": "bytes", + "name": "_extraOptions", + "type": "bytes" + } + ], + "name": "combineOptions", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimalConversionRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "endpoint", + "outputs": [ + { + "internalType": "contract ILayerZeroEndpointV2", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "eid", + "type": "uint32" + }, + { + "internalType": "uint16", + "name": "msgType", + "type": "uint16" + } + ], + "name": "enforcedOptions", + "outputs": [ + { + "internalType": "bytes", + "name": "enforcedOption", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "srcEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "internalType": "struct Origin", + "name": "", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_sender", + "type": "address" + } + ], + "name": "isComposeMsgSender", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_eid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "_peer", + "type": "bytes32" + } + ], + "name": "isPeer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "srcEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "internalType": "struct Origin", + "name": "_origin", + "type": "tuple" + }, + { + "internalType": "bytes32", + "name": "_guid", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "_message", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_executor", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_extraData", + "type": "bytes" + } + ], + "name": "lzReceive", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "uint32", + "name": "srcEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "internalType": "struct Origin", + "name": "origin", + "type": "tuple" + }, + { + "internalType": "uint32", + "name": "dstEid", + "type": "uint32" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "guid", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "address", + "name": "executor", + "type": "address" + }, + { + "internalType": "bytes", + "name": "message", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "internalType": "struct InboundPacket[]", + "name": "_packets", + "type": "tuple[]" + } + ], + "name": "lzReceiveAndRevert", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "srcEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "internalType": "struct Origin", + "name": "_origin", + "type": "tuple" + }, + { + "internalType": "bytes32", + "name": "_guid", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "_message", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_executor", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_extraData", + "type": "bytes" + } + ], + "name": "lzReceiveSimulate", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "msgInspector", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "nextNonce", + "outputs": [ + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oApp", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oAppVersion", + "outputs": [ + { + "internalType": "uint64", + "name": "senderVersion", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "receiverVersion", + "type": "uint64" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "oftVersion", + "outputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + }, + { + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "eid", + "type": "uint32" + } + ], + "name": "peers", + "outputs": [ + { + "internalType": "bytes32", + "name": "peer", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "preCrime", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "dstEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "to", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amountLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountLD", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "extraOptions", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "composeMsg", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "oftCmd", + "type": "bytes" + } + ], + "internalType": "struct SendParam", + "name": "_sendParam", + "type": "tuple" + } + ], + "name": "quoteOFT", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "minAmountLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxAmountLD", + "type": "uint256" + } + ], + "internalType": "struct OFTLimit", + "name": "oftLimit", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "int256", + "name": "feeAmountLD", + "type": "int256" + }, + { + "internalType": "string", + "name": "description", + "type": "string" + } + ], + "internalType": "struct OFTFeeDetail[]", + "name": "oftFeeDetails", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "amountSentLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountReceivedLD", + "type": "uint256" + } + ], + "internalType": "struct OFTReceipt", + "name": "oftReceipt", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "dstEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "to", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amountLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountLD", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "extraOptions", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "composeMsg", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "oftCmd", + "type": "bytes" + } + ], + "internalType": "struct SendParam", + "name": "_sendParam", + "type": "tuple" + }, + { + "internalType": "bool", + "name": "_payInLzToken", + "type": "bool" + } + ], + "name": "quoteSend", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "nativeFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lzTokenFee", + "type": "uint256" + } + ], + "internalType": "struct MessagingFee", + "name": "msgFee", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "dstEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "to", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amountLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountLD", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "extraOptions", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "composeMsg", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "oftCmd", + "type": "bytes" + } + ], + "internalType": "struct SendParam", + "name": "_sendParam", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "nativeFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lzTokenFee", + "type": "uint256" + } + ], + "internalType": "struct MessagingFee", + "name": "_fee", + "type": "tuple" + }, + { + "internalType": "address", + "name": "_refundAddress", + "type": "address" + } + ], + "name": "send", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "guid", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "nativeFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lzTokenFee", + "type": "uint256" + } + ], + "internalType": "struct MessagingFee", + "name": "fee", + "type": "tuple" + } + ], + "internalType": "struct MessagingReceipt", + "name": "msgReceipt", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "amountSentLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountReceivedLD", + "type": "uint256" + } + ], + "internalType": "struct OFTReceipt", + "name": "oftReceipt", + "type": "tuple" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_delegate", + "type": "address" + } + ], + "name": "setDelegate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "eid", + "type": "uint32" + }, + { + "internalType": "uint16", + "name": "msgType", + "type": "uint16" + }, + { + "internalType": "bytes", + "name": "options", + "type": "bytes" + } + ], + "internalType": "struct EnforcedOptionParam[]", + "name": "_enforcedOptions", + "type": "tuple[]" + } + ], + "name": "setEnforcedOptions", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_msgInspector", + "type": "address" + } + ], + "name": "setMsgInspector", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_eid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "_peer", + "type": "bytes32" + } + ], + "name": "setPeer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_preCrime", + "type": "address" + } + ], + "name": "setPreCrime", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "sharedDecimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "transactionHash": "0xe77893e9edb86bff04ac8ce2e05125afc2c0452f90485a4e7e0da149f65e8e4d", + "receipt": { + "to": null, + "from": "0x8583894d0e57e42abb83039537f314490038efa0", + "contractAddress": "0xf970027c806420a0222F5b72b097d3fDeC81b228", + "transactionIndex": 1, + "gasUsed": "2887152", + "logsBloom": "0x00000000000000000000000000000000000000000000000000800000000000000000020000000000220000000000000000000002000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000100020000000200000000000800000000000000000000000000000000400000000000000020000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000020000", + "blockHash": "0x9d7839b1773ac15a2efed2d5e7240554cff489ed74b2a773a4e1b08b96f77de9", + "transactionHash": "0xe77893e9edb86bff04ac8ce2e05125afc2c0452f90485a4e7e0da149f65e8e4d", + "logs": [ + { + "transactionIndex": 1, + "blockNumber": 46865431, + "transactionHash": "0xe77893e9edb86bff04ac8ce2e05125afc2c0452f90485a4e7e0da149f65e8e4d", + "address": "0xf970027c806420a0222F5b72b097d3fDeC81b228", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000008583894d0e57e42abb83039537f314490038efa0" + ], + "data": "0x", + "logIndex": 0, + "blockHash": "0x9d7839b1773ac15a2efed2d5e7240554cff489ed74b2a773a4e1b08b96f77de9" + }, + { + "transactionIndex": 1, + "blockNumber": 46865431, + "transactionHash": "0xe77893e9edb86bff04ac8ce2e05125afc2c0452f90485a4e7e0da149f65e8e4d", + "address": "0x6EDCE65403992e310A62460808c4b910D972f10f", + "topics": [ + "0x6ee10e9ed4d6ce9742703a498707862f4b00f1396a87195eb93267b3d7983981" + ], + "data": "0x000000000000000000000000f970027c806420a0222f5b72b097d3fdec81b2280000000000000000000000008583894d0e57e42abb83039537f314490038efa0", + "logIndex": 1, + "blockHash": "0x9d7839b1773ac15a2efed2d5e7240554cff489ed74b2a773a4e1b08b96f77de9" + } + ], + "blockNumber": 46865431, + "cumulativeGasUsed": "2933382", + "status": 1, + "byzantium": true + }, + "args": [ + "USD Coin", + "USDC", + "0x6EDCE65403992e310A62460808c4b910D972f10f", + "0x8583894d0e57e42abb83039537f314490038efa0" + ], + "numDeployments": 1, + "solcInputHash": "8088d7b064191499b181ffda0ed40a97", + "metadata": "{\"compiler\":{\"version\":\"0.8.22+commit.4fc1097e\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_name\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"_symbol\",\"type\":\"string\"},{\"internalType\":\"address\",\"name\":\"_lzEndpoint\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_delegate\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amountSD\",\"type\":\"uint256\"}],\"name\":\"AmountSDOverflowed\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"allowance\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"needed\",\"type\":\"uint256\"}],\"name\":\"ERC20InsufficientAllowance\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"needed\",\"type\":\"uint256\"}],\"name\":\"ERC20InsufficientBalance\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"approver\",\"type\":\"address\"}],\"name\":\"ERC20InvalidApprover\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"}],\"name\":\"ERC20InvalidReceiver\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"ERC20InvalidSender\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"ERC20InvalidSpender\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidDelegate\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidEndpointCall\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidLocalDecimals\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"options\",\"type\":\"bytes\"}],\"name\":\"InvalidOptions\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"LzTokenUnavailable\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"}],\"name\":\"NoPeer\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"msgValue\",\"type\":\"uint256\"}],\"name\":\"NotEnoughNative\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"OnlyEndpoint\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"}],\"name\":\"OnlyPeer\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlySelf\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"OwnableInvalidOwner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"OwnableUnauthorizedAccount\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"}],\"name\":\"SafeERC20FailedOperation\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"result\",\"type\":\"bytes\"}],\"name\":\"SimulationResult\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amountLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minAmountLD\",\"type\":\"uint256\"}],\"name\":\"SlippageExceeded\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"msgType\",\"type\":\"uint16\"},{\"internalType\":\"bytes\",\"name\":\"options\",\"type\":\"bytes\"}],\"indexed\":false,\"internalType\":\"struct EnforcedOptionParam[]\",\"name\":\"_enforcedOptions\",\"type\":\"tuple[]\"}],\"name\":\"EnforcedOptionSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"inspector\",\"type\":\"address\"}],\"name\":\"MsgInspectorSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"guid\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"srcEid\",\"type\":\"uint32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"toAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amountReceivedLD\",\"type\":\"uint256\"}],\"name\":\"OFTReceived\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"guid\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"fromAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amountSentLD\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amountReceivedLD\",\"type\":\"uint256\"}],\"name\":\"OFTSent\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"peer\",\"type\":\"bytes32\"}],\"name\":\"PeerSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"preCrimeAddress\",\"type\":\"address\"}],\"name\":\"PreCrimeSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"SEND\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"\",\"type\":\"uint16\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"SEND_AND_CALL\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"\",\"type\":\"uint16\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"srcEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"struct Origin\",\"name\":\"origin\",\"type\":\"tuple\"}],\"name\":\"allowInitializePath\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"approvalRequired\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"_eid\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"_msgType\",\"type\":\"uint16\"},{\"internalType\":\"bytes\",\"name\":\"_extraOptions\",\"type\":\"bytes\"}],\"name\":\"combineOptions\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimalConversionRate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"endpoint\",\"outputs\":[{\"internalType\":\"contract ILayerZeroEndpointV2\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"msgType\",\"type\":\"uint16\"}],\"name\":\"enforcedOptions\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"enforcedOption\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"srcEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"struct Origin\",\"name\":\"\",\"type\":\"tuple\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"_sender\",\"type\":\"address\"}],\"name\":\"isComposeMsgSender\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"_eid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"_peer\",\"type\":\"bytes32\"}],\"name\":\"isPeer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"srcEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"struct Origin\",\"name\":\"_origin\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"_guid\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"_message\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"_executor\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_extraData\",\"type\":\"bytes\"}],\"name\":\"lzReceive\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"srcEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"struct Origin\",\"name\":\"origin\",\"type\":\"tuple\"},{\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"guid\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"executor\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"message\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"extraData\",\"type\":\"bytes\"}],\"internalType\":\"struct InboundPacket[]\",\"name\":\"_packets\",\"type\":\"tuple[]\"}],\"name\":\"lzReceiveAndRevert\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"srcEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"struct Origin\",\"name\":\"_origin\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"_guid\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"_message\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"_executor\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_extraData\",\"type\":\"bytes\"}],\"name\":\"lzReceiveSimulate\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"mint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"msgInspector\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"nextNonce\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"oApp\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"oAppVersion\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"senderVersion\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"receiverVersion\",\"type\":\"uint64\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"oftVersion\",\"outputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"},{\"internalType\":\"uint64\",\"name\":\"version\",\"type\":\"uint64\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"}],\"name\":\"peers\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"peer\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"preCrime\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"to\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"amountLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minAmountLD\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"extraOptions\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"composeMsg\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"oftCmd\",\"type\":\"bytes\"}],\"internalType\":\"struct SendParam\",\"name\":\"_sendParam\",\"type\":\"tuple\"}],\"name\":\"quoteOFT\",\"outputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"minAmountLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxAmountLD\",\"type\":\"uint256\"}],\"internalType\":\"struct OFTLimit\",\"name\":\"oftLimit\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"int256\",\"name\":\"feeAmountLD\",\"type\":\"int256\"},{\"internalType\":\"string\",\"name\":\"description\",\"type\":\"string\"}],\"internalType\":\"struct OFTFeeDetail[]\",\"name\":\"oftFeeDetails\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"amountSentLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountReceivedLD\",\"type\":\"uint256\"}],\"internalType\":\"struct OFTReceipt\",\"name\":\"oftReceipt\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"to\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"amountLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minAmountLD\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"extraOptions\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"composeMsg\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"oftCmd\",\"type\":\"bytes\"}],\"internalType\":\"struct SendParam\",\"name\":\"_sendParam\",\"type\":\"tuple\"},{\"internalType\":\"bool\",\"name\":\"_payInLzToken\",\"type\":\"bool\"}],\"name\":\"quoteSend\",\"outputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"nativeFee\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"lzTokenFee\",\"type\":\"uint256\"}],\"internalType\":\"struct MessagingFee\",\"name\":\"msgFee\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"to\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"amountLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minAmountLD\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"extraOptions\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"composeMsg\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"oftCmd\",\"type\":\"bytes\"}],\"internalType\":\"struct SendParam\",\"name\":\"_sendParam\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"nativeFee\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"lzTokenFee\",\"type\":\"uint256\"}],\"internalType\":\"struct MessagingFee\",\"name\":\"_fee\",\"type\":\"tuple\"},{\"internalType\":\"address\",\"name\":\"_refundAddress\",\"type\":\"address\"}],\"name\":\"send\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"guid\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"nativeFee\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"lzTokenFee\",\"type\":\"uint256\"}],\"internalType\":\"struct MessagingFee\",\"name\":\"fee\",\"type\":\"tuple\"}],\"internalType\":\"struct MessagingReceipt\",\"name\":\"msgReceipt\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"amountSentLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountReceivedLD\",\"type\":\"uint256\"}],\"internalType\":\"struct OFTReceipt\",\"name\":\"oftReceipt\",\"type\":\"tuple\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_delegate\",\"type\":\"address\"}],\"name\":\"setDelegate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"msgType\",\"type\":\"uint16\"},{\"internalType\":\"bytes\",\"name\":\"options\",\"type\":\"bytes\"}],\"internalType\":\"struct EnforcedOptionParam[]\",\"name\":\"_enforcedOptions\",\"type\":\"tuple[]\"}],\"name\":\"setEnforcedOptions\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_msgInspector\",\"type\":\"address\"}],\"name\":\"setMsgInspector\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"_eid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"_peer\",\"type\":\"bytes32\"}],\"name\":\"setPeer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_preCrime\",\"type\":\"address\"}],\"name\":\"setPreCrime\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"sharedDecimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"errors\":{\"ERC20InsufficientAllowance(address,uint256,uint256)\":[{\"details\":\"Indicates a failure with the `spender`\\u2019s `allowance`. Used in transfers.\",\"params\":{\"allowance\":\"Amount of tokens a `spender` is allowed to operate with.\",\"needed\":\"Minimum amount required to perform a transfer.\",\"spender\":\"Address that may be allowed to operate on tokens without being their owner.\"}}],\"ERC20InsufficientBalance(address,uint256,uint256)\":[{\"details\":\"Indicates an error related to the current `balance` of a `sender`. Used in transfers.\",\"params\":{\"balance\":\"Current balance for the interacting account.\",\"needed\":\"Minimum amount required to perform a transfer.\",\"sender\":\"Address whose tokens are being transferred.\"}}],\"ERC20InvalidApprover(address)\":[{\"details\":\"Indicates a failure with the `approver` of a token to be approved. Used in approvals.\",\"params\":{\"approver\":\"Address initiating an approval operation.\"}}],\"ERC20InvalidReceiver(address)\":[{\"details\":\"Indicates a failure with the token `receiver`. Used in transfers.\",\"params\":{\"receiver\":\"Address to which tokens are being transferred.\"}}],\"ERC20InvalidSender(address)\":[{\"details\":\"Indicates a failure with the token `sender`. Used in transfers.\",\"params\":{\"sender\":\"Address whose tokens are being transferred.\"}}],\"ERC20InvalidSpender(address)\":[{\"details\":\"Indicates a failure with the `spender` to be approved. Used in approvals.\",\"params\":{\"spender\":\"Address that may be allowed to operate on tokens without being their owner.\"}}],\"OwnableInvalidOwner(address)\":[{\"details\":\"The owner is not a valid owner account. (eg. `address(0)`)\"}],\"OwnableUnauthorizedAccount(address)\":[{\"details\":\"The caller account is not authorized to perform an operation.\"}],\"SafeERC20FailedOperation(address)\":[{\"details\":\"An operation with an ERC-20 token failed.\"}]},\"events\":{\"Approval(address,address,uint256)\":{\"details\":\"Emitted when the allowance of a `spender` for an `owner` is set by a call to {approve}. `value` is the new allowance.\"},\"PreCrimeSet(address)\":{\"details\":\"Emitted when the preCrime contract address is set.\",\"params\":{\"preCrimeAddress\":\"The address of the preCrime contract.\"}},\"Transfer(address,address,uint256)\":{\"details\":\"Emitted when `value` tokens are moved from one account (`from`) to another (`to`). Note that `value` may be zero.\"}},\"kind\":\"dev\",\"methods\":{\"allowInitializePath((uint32,bytes32,uint64))\":{\"details\":\"This indicates to the endpoint that the OApp has enabled msgs for this particular path to be received.This defaults to assuming if a peer has been set, its initialized. Can be overridden by the OApp if there is other logic to determine this.\",\"params\":{\"origin\":\"The origin information containing the source endpoint and sender address.\"},\"returns\":{\"_0\":\"Whether the path has been initialized.\"}},\"allowance(address,address)\":{\"details\":\"Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` through {transferFrom}. This is zero by default. This value changes when {approve} or {transferFrom} are called.\"},\"approvalRequired()\":{\"details\":\"In the case of OFT where the contract IS the token, approval is NOT required.\",\"returns\":{\"_0\":\"requiresApproval Needs approval of the underlying token implementation.\"}},\"approve(address,uint256)\":{\"details\":\"See {IERC20-approve}. NOTE: If `value` is the maximum `uint256`, the allowance is not updated on `transferFrom`. This is semantically equivalent to an infinite approval. Requirements: - `spender` cannot be the zero address.\"},\"balanceOf(address)\":{\"details\":\"Returns the value of tokens owned by `account`.\"},\"combineOptions(uint32,uint16,bytes)\":{\"details\":\"If there is an enforced lzReceive option: - {gasLimit: 200k, msg.value: 1 ether} AND a caller supplies a lzReceive option: {gasLimit: 100k, msg.value: 0.5 ether} - The resulting options will be {gasLimit: 300k, msg.value: 1.5 ether} when the message is executed on the remote lzReceive() function.This presence of duplicated options is handled off-chain in the verifier/executor.\",\"params\":{\"_eid\":\"The endpoint ID.\",\"_extraOptions\":\"Additional options passed by the caller.\",\"_msgType\":\"The OAPP message type.\"},\"returns\":{\"_0\":\"options The combination of caller specified options AND enforced options.\"}},\"decimals()\":{\"details\":\"Returns the number of decimals used to get its user representation. For example, if `decimals` equals `2`, a balance of `505` tokens should be displayed to a user as `5.05` (`505 / 10 ** 2`). Tokens usually opt for a value of 18, imitating the relationship between Ether and Wei. This is the default value returned by this function, unless it's overridden. NOTE: This information is only used for _display_ purposes: it in no way affects any of the arithmetic of the contract, including {IERC20-balanceOf} and {IERC20-transfer}.\"},\"isComposeMsgSender((uint32,bytes32,uint64),bytes,address)\":{\"details\":\"_origin The origin information containing the source endpoint and sender address. - srcEid: The source chain endpoint ID. - sender: The sender address on the src chain. - nonce: The nonce of the message._message The lzReceive payload.Applications can optionally choose to implement separate composeMsg senders that are NOT the bridging layer.The default sender IS the OAppReceiver implementer.\",\"params\":{\"_sender\":\"The sender address.\"},\"returns\":{\"_0\":\"isSender Is a valid sender.\"}},\"isPeer(uint32,bytes32)\":{\"details\":\"Check if the peer is considered 'trusted' by the OApp.Enables OAppPreCrimeSimulator to check whether a potential Inbound Packet is from a trusted source.\",\"params\":{\"_eid\":\"The endpoint ID to check.\",\"_peer\":\"The peer to check.\"},\"returns\":{\"_0\":\"Whether the peer passed is considered 'trusted' by the OApp.\"}},\"lzReceive((uint32,bytes32,uint64),bytes32,bytes,address,bytes)\":{\"details\":\"Entry point for receiving messages or packets from the endpoint.Entry point for receiving msg/packet from the LayerZero endpoint.\",\"params\":{\"_executor\":\"The address of the executor for the received message.\",\"_extraData\":\"Additional arbitrary data provided by the corresponding executor.\",\"_guid\":\"The unique identifier for the received LayerZero message.\",\"_message\":\"The payload of the received message.\",\"_origin\":\"The origin information containing the source endpoint and sender address. - srcEid: The source chain endpoint ID. - sender: The sender address on the src chain. - nonce: The nonce of the message.\"}},\"lzReceiveAndRevert(((uint32,bytes32,uint64),uint32,address,bytes32,uint256,address,bytes,bytes)[])\":{\"details\":\"Interface for pre-crime simulations. Always reverts at the end with the simulation results.WARNING: MUST revert at the end with the simulation results.Gives the preCrime implementation the ability to mock sending packets to the lzReceive function, WITHOUT actually executing them.\",\"params\":{\"_packets\":\"An array of InboundPacket objects representing received packets to be delivered.\"}},\"lzReceiveSimulate((uint32,bytes32,uint64),bytes32,bytes,address,bytes)\":{\"details\":\"Is effectively an internal function because msg.sender must be address(this). Allows resetting the call stack for 'internal' calls.\",\"params\":{\"_executor\":\"The executor address for the packet.\",\"_extraData\":\"Additional data for the packet.\",\"_guid\":\"The unique identifier of the packet.\",\"_message\":\"The message payload of the packet.\",\"_origin\":\"The origin information containing the source endpoint and sender address. - srcEid: The source chain endpoint ID. - sender: The sender address on the src chain. - nonce: The nonce of the message.\"}},\"mint(address,uint256)\":{\"details\":\"Deliberately unguarded: the demo mints to itself before each send, and a testnet OFT with no way to obtain tokens cannot be used to exercise the DVN at all. Do NOT ship this to a network where the token has value. `virtual` because MyOFTMock declares the same function for the hardhat tests.\"},\"name()\":{\"details\":\"Returns the name of the token.\"},\"nextNonce(uint32,bytes32)\":{\"details\":\"_srcEid The source endpoint ID._sender The sender address.The path nonce starts from 1. If 0 is returned it means that there is NO nonce ordered enforcement.Is required by the off-chain executor to determine the OApp expects msg execution is ordered.This is also enforced by the OApp.By default this is NOT enabled. ie. nextNonce is hardcoded to return 0.\",\"returns\":{\"nonce\":\"The next nonce.\"}},\"oApp()\":{\"details\":\"Retrieves the address of the OApp contract.The simulator contract is the base contract for the OApp by default.If the simulator is a separate contract, override this function.\",\"returns\":{\"_0\":\"The address of the OApp contract.\"}},\"oAppVersion()\":{\"returns\":{\"receiverVersion\":\"The version of the OAppReceiver.sol implementation.\",\"senderVersion\":\"The version of the OAppSender.sol implementation.\"}},\"oftVersion()\":{\"details\":\"interfaceId: This specific interface ID is '0x02e49c2c'.version: Indicates a cross-chain compatible msg encoding with other OFTs.If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented. ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)\",\"returns\":{\"interfaceId\":\"The interface ID.\",\"version\":\"The version.\"}},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"quoteOFT((uint32,bytes32,uint256,uint256,bytes,bytes,bytes))\":{\"params\":{\"_sendParam\":\"The parameters for the send operation.\"},\"returns\":{\"oftFeeDetails\":\"The details of OFT fees.\",\"oftLimit\":\"The OFT limit information.\",\"oftReceipt\":\"The OFT receipt information.\"}},\"quoteSend((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),bool)\":{\"details\":\"MessagingFee: LayerZero msg fee - nativeFee: The native fee. - lzTokenFee: The lzToken fee.\",\"params\":{\"_payInLzToken\":\"Flag indicating whether the caller is paying in the LZ token.\",\"_sendParam\":\"The parameters for the send() operation.\"},\"returns\":{\"msgFee\":\"The calculated LayerZero messaging fee from the send() operation.\"}},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner.\"},\"send((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),(uint256,uint256),address)\":{\"details\":\"Executes the send operation.MessagingReceipt: LayerZero msg receipt - guid: The unique identifier for the sent message. - nonce: The nonce of the sent message. - fee: The LayerZero fee incurred for the message.\",\"params\":{\"_fee\":\"The calculated fee for the send() operation. - nativeFee: The native fee. - lzTokenFee: The lzToken fee.\",\"_refundAddress\":\"The address to receive any excess funds.\",\"_sendParam\":\"The parameters for the send operation.\"},\"returns\":{\"msgReceipt\":\"The receipt for the send operation.\",\"oftReceipt\":\"The OFT receipt information.\"}},\"setDelegate(address)\":{\"details\":\"Only the owner/admin of the OApp can call this function.Provides the ability for a delegate to set configs, on behalf of the OApp, directly on the Endpoint contract.\",\"params\":{\"_delegate\":\"The address of the delegate to be set.\"}},\"setEnforcedOptions((uint32,uint16,bytes)[])\":{\"details\":\"Sets the enforced options for specific endpoint and message type combinations.Only the owner/admin of the OApp can call this function.Provides a way for the OApp to enforce things like paying for PreCrime, AND/OR minimum dst lzReceive gas amounts etc.These enforced options can vary as the potential options/execution on the remote may differ as per the msgType. eg. Amount of lzReceive() gas necessary to deliver a lzCompose() message adds overhead you dont want to pay if you are only making a standard LayerZero message ie. lzReceive() WITHOUT sendCompose().\",\"params\":{\"_enforcedOptions\":\"An array of EnforcedOptionParam structures specifying enforced options.\"}},\"setMsgInspector(address)\":{\"details\":\"Sets the message inspector address for the OFT.This is an optional contract that can be used to inspect both 'message' and 'options'.Set it to address(0) to disable it, or set it to a contract address to enable it.\",\"params\":{\"_msgInspector\":\"The address of the message inspector.\"}},\"setPeer(uint32,bytes32)\":{\"details\":\"Only the owner/admin of the OApp can call this function.Indicates that the peer is trusted to send LayerZero messages to this OApp.Set this to bytes32(0) to remove the peer address.Peer is a bytes32 to accommodate non-evm chains.\",\"params\":{\"_eid\":\"The endpoint ID.\",\"_peer\":\"The address of the peer to be associated with the corresponding endpoint.\"}},\"setPreCrime(address)\":{\"details\":\"Sets the preCrime contract address.\",\"params\":{\"_preCrime\":\"The address of the preCrime contract.\"}},\"sharedDecimals()\":{\"details\":\"Retrieves the shared decimals of the OFT.Sets an implicit cap on the amount of tokens, over uint64.max() will need some sort of outbound cap / totalSupply cap Lowest common decimal denominator between chains. Defaults to 6 decimal places to provide up to 18,446,744,073,709.551615 units (max uint64). For tokens exceeding this totalSupply(), they will need to override the sharedDecimals function with something smaller. ie. 4 sharedDecimals would be 1,844,674,407,370,955.1615\",\"returns\":{\"_0\":\"The shared decimals of the OFT.\"}},\"symbol()\":{\"details\":\"Returns the symbol of the token, usually a shorter version of the name.\"},\"token()\":{\"details\":\"Retrieves the address of the underlying ERC20 implementation.In the case of OFT, address(this) and erc20 are the same contract.\",\"returns\":{\"_0\":\"The address of the OFT token.\"}},\"totalSupply()\":{\"details\":\"Returns the value of tokens in existence.\"},\"transfer(address,uint256)\":{\"details\":\"See {IERC20-transfer}. Requirements: - `to` cannot be the zero address. - the caller must have a balance of at least `value`.\"},\"transferFrom(address,address,uint256)\":{\"details\":\"See {IERC20-transferFrom}. Skips emitting an {Approval} event indicating an allowance update. This is not required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve]. NOTE: Does not update the allowance if the current allowance is the maximum `uint256`. Requirements: - `from` and `to` cannot be the zero address. - `from` must have a balance of at least `value`. - the caller must have allowance for ``from``'s tokens of at least `value`.\"},\"transferOwnership(address)\":{\"details\":\"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"allowInitializePath((uint32,bytes32,uint64))\":{\"notice\":\"Checks if the path initialization is allowed based on the provided origin.\"},\"approvalRequired()\":{\"notice\":\"Indicates whether the OFT contract requires approval of the 'token()' to send.\"},\"combineOptions(uint32,uint16,bytes)\":{\"notice\":\"Combines options for a given endpoint and message type.\"},\"endpoint()\":{\"notice\":\"Retrieves the LayerZero endpoint associated with the OApp.\"},\"isComposeMsgSender((uint32,bytes32,uint64),bytes,address)\":{\"notice\":\"Indicates whether an address is an approved composeMsg sender to the Endpoint.\"},\"mint(address,uint256)\":{\"notice\":\"Open mint, TESTNET ONLY \\u2014 same as ToyOFT, so the demo tasks work against either.\"},\"nextNonce(uint32,bytes32)\":{\"notice\":\"Retrieves the next nonce for a given source endpoint and sender address.\"},\"oAppVersion()\":{\"notice\":\"Retrieves the OApp version information.\"},\"oftVersion()\":{\"notice\":\"Retrieves interfaceID and the version of the OFT.\"},\"peers(uint32)\":{\"notice\":\"Retrieves the peer (OApp) associated with a corresponding endpoint.\"},\"quoteOFT((uint32,bytes32,uint256,uint256,bytes,bytes,bytes))\":{\"notice\":\"Provides the fee breakdown and settings data for an OFT. Unused in the default implementation.\"},\"quoteSend((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),bool)\":{\"notice\":\"Provides a quote for the send() operation.\"},\"setDelegate(address)\":{\"notice\":\"Sets the delegate address for the OApp.\"},\"setPeer(uint32,bytes32)\":{\"notice\":\"Sets the peer address (OApp instance) for a corresponding endpoint.\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/MyOFT.sol\":\"MyOFT\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\nimport { IMessageLibManager } from \\\"./IMessageLibManager.sol\\\";\\nimport { IMessagingComposer } from \\\"./IMessagingComposer.sol\\\";\\nimport { IMessagingChannel } from \\\"./IMessagingChannel.sol\\\";\\nimport { IMessagingContext } from \\\"./IMessagingContext.sol\\\";\\n\\nstruct MessagingParams {\\n uint32 dstEid;\\n bytes32 receiver;\\n bytes message;\\n bytes options;\\n bool payInLzToken;\\n}\\n\\nstruct MessagingReceipt {\\n bytes32 guid;\\n uint64 nonce;\\n MessagingFee fee;\\n}\\n\\nstruct MessagingFee {\\n uint256 nativeFee;\\n uint256 lzTokenFee;\\n}\\n\\nstruct Origin {\\n uint32 srcEid;\\n bytes32 sender;\\n uint64 nonce;\\n}\\n\\ninterface ILayerZeroEndpointV2 is IMessageLibManager, IMessagingComposer, IMessagingChannel, IMessagingContext {\\n event PacketSent(bytes encodedPayload, bytes options, address sendLibrary);\\n\\n event PacketVerified(Origin origin, address receiver, bytes32 payloadHash);\\n\\n event PacketDelivered(Origin origin, address receiver);\\n\\n event LzReceiveAlert(\\n address indexed receiver,\\n address indexed executor,\\n Origin origin,\\n bytes32 guid,\\n uint256 gas,\\n uint256 value,\\n bytes message,\\n bytes extraData,\\n bytes reason\\n );\\n\\n event LzTokenSet(address token);\\n\\n event DelegateSet(address sender, address delegate);\\n\\n function quote(MessagingParams calldata _params, address _sender) external view returns (MessagingFee memory);\\n\\n function send(\\n MessagingParams calldata _params,\\n address _refundAddress\\n ) external payable returns (MessagingReceipt memory);\\n\\n function verify(Origin calldata _origin, address _receiver, bytes32 _payloadHash) external;\\n\\n function verifiable(Origin calldata _origin, address _receiver) external view returns (bool);\\n\\n function initializable(Origin calldata _origin, address _receiver) external view returns (bool);\\n\\n function lzReceive(\\n Origin calldata _origin,\\n address _receiver,\\n bytes32 _guid,\\n bytes calldata _message,\\n bytes calldata _extraData\\n ) external payable;\\n\\n // oapp can burn messages partially by calling this function with its own business logic if messages are verified in order\\n function clear(address _oapp, Origin calldata _origin, bytes32 _guid, bytes calldata _message) external;\\n\\n function setLzToken(address _lzToken) external;\\n\\n function lzToken() external view returns (address);\\n\\n function nativeToken() external view returns (address);\\n\\n function setDelegate(address _delegate) external;\\n}\\n\",\"keccak256\":\"0xf7f941bee89ea6369950fe54e8ac476ae6478b958b20fc0e8a83e8ff1364eac3\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroReceiver.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\nimport { Origin } from \\\"./ILayerZeroEndpointV2.sol\\\";\\n\\ninterface ILayerZeroReceiver {\\n function allowInitializePath(Origin calldata _origin) external view returns (bool);\\n\\n function nextNonce(uint32 _eid, bytes32 _sender) external view returns (uint64);\\n\\n function lzReceive(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address _executor,\\n bytes calldata _extraData\\n ) external payable;\\n}\\n\",\"keccak256\":\"0x9641abba8d53b08bb517d1b74801dd15ea7b84d77a6719085bd96c8ea94e3ca0\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLib.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\nimport { IERC165 } from \\\"@openzeppelin/contracts/utils/introspection/IERC165.sol\\\";\\n\\nimport { SetConfigParam } from \\\"./IMessageLibManager.sol\\\";\\n\\nenum MessageLibType {\\n Send,\\n Receive,\\n SendAndReceive\\n}\\n\\ninterface IMessageLib is IERC165 {\\n function setConfig(address _oapp, SetConfigParam[] calldata _config) external;\\n\\n function getConfig(uint32 _eid, address _oapp, uint32 _configType) external view returns (bytes memory config);\\n\\n function isSupportedEid(uint32 _eid) external view returns (bool);\\n\\n // message libs of same major version are compatible\\n function version() external view returns (uint64 major, uint8 minor, uint8 endpointVersion);\\n\\n function messageLibType() external view returns (MessageLibType);\\n}\\n\",\"keccak256\":\"0x5cf5f24751b4e3ea1c9c5ded07cedfdfd62566b6daaffcc0144733859c9dba0c\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLibManager.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\nstruct SetConfigParam {\\n uint32 eid;\\n uint32 configType;\\n bytes config;\\n}\\n\\ninterface IMessageLibManager {\\n struct Timeout {\\n address lib;\\n uint256 expiry;\\n }\\n\\n event LibraryRegistered(address newLib);\\n event DefaultSendLibrarySet(uint32 eid, address newLib);\\n event DefaultReceiveLibrarySet(uint32 eid, address newLib);\\n event DefaultReceiveLibraryTimeoutSet(uint32 eid, address oldLib, uint256 expiry);\\n event SendLibrarySet(address sender, uint32 eid, address newLib);\\n event ReceiveLibrarySet(address receiver, uint32 eid, address newLib);\\n event ReceiveLibraryTimeoutSet(address receiver, uint32 eid, address oldLib, uint256 timeout);\\n\\n function registerLibrary(address _lib) external;\\n\\n function isRegisteredLibrary(address _lib) external view returns (bool);\\n\\n function getRegisteredLibraries() external view returns (address[] memory);\\n\\n function setDefaultSendLibrary(uint32 _eid, address _newLib) external;\\n\\n function defaultSendLibrary(uint32 _eid) external view returns (address);\\n\\n function setDefaultReceiveLibrary(uint32 _eid, address _newLib, uint256 _gracePeriod) external;\\n\\n function defaultReceiveLibrary(uint32 _eid) external view returns (address);\\n\\n function setDefaultReceiveLibraryTimeout(uint32 _eid, address _lib, uint256 _expiry) external;\\n\\n function defaultReceiveLibraryTimeout(uint32 _eid) external view returns (address lib, uint256 expiry);\\n\\n function isSupportedEid(uint32 _eid) external view returns (bool);\\n\\n function isValidReceiveLibrary(address _receiver, uint32 _eid, address _lib) external view returns (bool);\\n\\n /// ------------------- OApp interfaces -------------------\\n function setSendLibrary(address _oapp, uint32 _eid, address _newLib) external;\\n\\n function getSendLibrary(address _sender, uint32 _eid) external view returns (address lib);\\n\\n function isDefaultSendLibrary(address _sender, uint32 _eid) external view returns (bool);\\n\\n function setReceiveLibrary(address _oapp, uint32 _eid, address _newLib, uint256 _gracePeriod) external;\\n\\n function getReceiveLibrary(address _receiver, uint32 _eid) external view returns (address lib, bool isDefault);\\n\\n function setReceiveLibraryTimeout(address _oapp, uint32 _eid, address _lib, uint256 _expiry) external;\\n\\n function receiveLibraryTimeout(address _receiver, uint32 _eid) external view returns (address lib, uint256 expiry);\\n\\n function setConfig(address _oapp, address _lib, SetConfigParam[] calldata _params) external;\\n\\n function getConfig(\\n address _oapp,\\n address _lib,\\n uint32 _eid,\\n uint32 _configType\\n ) external view returns (bytes memory config);\\n}\\n\",\"keccak256\":\"0x919b37133adff4dc528e3061deb2789c3149971b530c61e556fb3d09ab315dfc\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingChannel.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\ninterface IMessagingChannel {\\n event InboundNonceSkipped(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce);\\n event PacketNilified(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce, bytes32 payloadHash);\\n event PacketBurnt(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce, bytes32 payloadHash);\\n\\n function eid() external view returns (uint32);\\n\\n // this is an emergency function if a message cannot be verified for some reasons\\n // required to provide _nextNonce to avoid race condition\\n function skip(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce) external;\\n\\n function nilify(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes32 _payloadHash) external;\\n\\n function burn(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes32 _payloadHash) external;\\n\\n function nextGuid(address _sender, uint32 _dstEid, bytes32 _receiver) external view returns (bytes32);\\n\\n function inboundNonce(address _receiver, uint32 _srcEid, bytes32 _sender) external view returns (uint64);\\n\\n function outboundNonce(address _sender, uint32 _dstEid, bytes32 _receiver) external view returns (uint64);\\n\\n function inboundPayloadHash(\\n address _receiver,\\n uint32 _srcEid,\\n bytes32 _sender,\\n uint64 _nonce\\n ) external view returns (bytes32);\\n\\n function lazyInboundNonce(address _receiver, uint32 _srcEid, bytes32 _sender) external view returns (uint64);\\n}\\n\",\"keccak256\":\"0x0878f64dffebf58c4165569416372f40860fab546b88cd926eba0d5cb6d8d972\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingComposer.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\ninterface IMessagingComposer {\\n event ComposeSent(address from, address to, bytes32 guid, uint16 index, bytes message);\\n event ComposeDelivered(address from, address to, bytes32 guid, uint16 index);\\n event LzComposeAlert(\\n address indexed from,\\n address indexed to,\\n address indexed executor,\\n bytes32 guid,\\n uint16 index,\\n uint256 gas,\\n uint256 value,\\n bytes message,\\n bytes extraData,\\n bytes reason\\n );\\n\\n function composeQueue(\\n address _from,\\n address _to,\\n bytes32 _guid,\\n uint16 _index\\n ) external view returns (bytes32 messageHash);\\n\\n function sendCompose(address _to, bytes32 _guid, uint16 _index, bytes calldata _message) external;\\n\\n function lzCompose(\\n address _from,\\n address _to,\\n bytes32 _guid,\\n uint16 _index,\\n bytes calldata _message,\\n bytes calldata _extraData\\n ) external payable;\\n}\\n\",\"keccak256\":\"0x85bc7090134529ec474866dc4bb1c48692d518c756eb0a961c82574829c51901\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingContext.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\ninterface IMessagingContext {\\n function isSendingMessage() external view returns (bool);\\n\\n function getSendContext() external view returns (uint32 dstEid, address sender);\\n}\\n\",\"keccak256\":\"0xff0c546c2813dae3e440882f46b377375f7461b0714efd80bd3f0c6e5cb8da4e\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ISendLib.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\nimport { MessagingFee } from \\\"./ILayerZeroEndpointV2.sol\\\";\\nimport { IMessageLib } from \\\"./IMessageLib.sol\\\";\\n\\nstruct Packet {\\n uint64 nonce;\\n uint32 srcEid;\\n address sender;\\n uint32 dstEid;\\n bytes32 receiver;\\n bytes32 guid;\\n bytes message;\\n}\\n\\ninterface ISendLib is IMessageLib {\\n function send(\\n Packet calldata _packet,\\n bytes calldata _options,\\n bool _payInLzToken\\n ) external returns (MessagingFee memory, bytes memory encodedPacket);\\n\\n function quote(\\n Packet calldata _packet,\\n bytes calldata _options,\\n bool _payInLzToken\\n ) external view returns (MessagingFee memory);\\n\\n function setTreasury(address _treasury) external;\\n\\n function withdrawFee(address _to, uint256 _amount) external;\\n\\n function withdrawLzTokenFee(address _lzToken, address _to, uint256 _amount) external;\\n}\\n\",\"keccak256\":\"0xf1c07bc61e7b1dce195ed12d50f87980fbf2d63cac1326fd28287f55fe0ba625\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/libs/AddressCast.sol\":{\"content\":\"// SPDX-License-Identifier: LZBL-1.2\\n\\npragma solidity ^0.8.20;\\n\\nlibrary AddressCast {\\n error AddressCast_InvalidSizeForAddress();\\n error AddressCast_InvalidAddress();\\n\\n function toBytes32(bytes calldata _addressBytes) internal pure returns (bytes32 result) {\\n if (_addressBytes.length > 32) revert AddressCast_InvalidAddress();\\n result = bytes32(_addressBytes);\\n unchecked {\\n uint256 offset = 32 - _addressBytes.length;\\n result = result >> (offset * 8);\\n }\\n }\\n\\n function toBytes32(address _address) internal pure returns (bytes32 result) {\\n result = bytes32(uint256(uint160(_address)));\\n }\\n\\n function toBytes(bytes32 _addressBytes32, uint256 _size) internal pure returns (bytes memory result) {\\n if (_size == 0 || _size > 32) revert AddressCast_InvalidSizeForAddress();\\n result = new bytes(_size);\\n unchecked {\\n uint256 offset = 256 - _size * 8;\\n assembly {\\n mstore(add(result, 32), shl(offset, _addressBytes32))\\n }\\n }\\n }\\n\\n function toAddress(bytes32 _addressBytes32) internal pure returns (address result) {\\n result = address(uint160(uint256(_addressBytes32)));\\n }\\n\\n function toAddress(bytes calldata _addressBytes) internal pure returns (address result) {\\n if (_addressBytes.length != 20) revert AddressCast_InvalidAddress();\\n result = address(bytes20(_addressBytes));\\n }\\n}\\n\",\"keccak256\":\"0x2ebbcaaab3554edcd41b581f1a72ac1806afbfb8047d0d47ff098f9af30d6deb\",\"license\":\"LZBL-1.2\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.sol\":{\"content\":\"// SPDX-License-Identifier: LZBL-1.2\\n\\npragma solidity ^0.8.20;\\n\\nimport { Packet } from \\\"../../interfaces/ISendLib.sol\\\";\\nimport { AddressCast } from \\\"../../libs/AddressCast.sol\\\";\\n\\nlibrary PacketV1Codec {\\n using AddressCast for address;\\n using AddressCast for bytes32;\\n\\n uint8 internal constant PACKET_VERSION = 1;\\n\\n // header (version + nonce + path)\\n // version\\n uint256 private constant PACKET_VERSION_OFFSET = 0;\\n // nonce\\n uint256 private constant NONCE_OFFSET = 1;\\n // path\\n uint256 private constant SRC_EID_OFFSET = 9;\\n uint256 private constant SENDER_OFFSET = 13;\\n uint256 private constant DST_EID_OFFSET = 45;\\n uint256 private constant RECEIVER_OFFSET = 49;\\n // payload (guid + message)\\n uint256 private constant GUID_OFFSET = 81; // keccak256(nonce + path)\\n uint256 private constant MESSAGE_OFFSET = 113;\\n\\n function encode(Packet memory _packet) internal pure returns (bytes memory encodedPacket) {\\n encodedPacket = abi.encodePacked(\\n PACKET_VERSION,\\n _packet.nonce,\\n _packet.srcEid,\\n _packet.sender.toBytes32(),\\n _packet.dstEid,\\n _packet.receiver,\\n _packet.guid,\\n _packet.message\\n );\\n }\\n\\n function encodePacketHeader(Packet memory _packet) internal pure returns (bytes memory) {\\n return\\n abi.encodePacked(\\n PACKET_VERSION,\\n _packet.nonce,\\n _packet.srcEid,\\n _packet.sender.toBytes32(),\\n _packet.dstEid,\\n _packet.receiver\\n );\\n }\\n\\n function encodePayload(Packet memory _packet) internal pure returns (bytes memory) {\\n return abi.encodePacked(_packet.guid, _packet.message);\\n }\\n\\n function header(bytes calldata _packet) internal pure returns (bytes calldata) {\\n return _packet[0:GUID_OFFSET];\\n }\\n\\n function version(bytes calldata _packet) internal pure returns (uint8) {\\n return uint8(bytes1(_packet[PACKET_VERSION_OFFSET:NONCE_OFFSET]));\\n }\\n\\n function nonce(bytes calldata _packet) internal pure returns (uint64) {\\n return uint64(bytes8(_packet[NONCE_OFFSET:SRC_EID_OFFSET]));\\n }\\n\\n function srcEid(bytes calldata _packet) internal pure returns (uint32) {\\n return uint32(bytes4(_packet[SRC_EID_OFFSET:SENDER_OFFSET]));\\n }\\n\\n function sender(bytes calldata _packet) internal pure returns (bytes32) {\\n return bytes32(_packet[SENDER_OFFSET:DST_EID_OFFSET]);\\n }\\n\\n function senderAddressB20(bytes calldata _packet) internal pure returns (address) {\\n return sender(_packet).toAddress();\\n }\\n\\n function dstEid(bytes calldata _packet) internal pure returns (uint32) {\\n return uint32(bytes4(_packet[DST_EID_OFFSET:RECEIVER_OFFSET]));\\n }\\n\\n function receiver(bytes calldata _packet) internal pure returns (bytes32) {\\n return bytes32(_packet[RECEIVER_OFFSET:GUID_OFFSET]);\\n }\\n\\n function receiverB20(bytes calldata _packet) internal pure returns (address) {\\n return receiver(_packet).toAddress();\\n }\\n\\n function guid(bytes calldata _packet) internal pure returns (bytes32) {\\n return bytes32(_packet[GUID_OFFSET:MESSAGE_OFFSET]);\\n }\\n\\n function message(bytes calldata _packet) internal pure returns (bytes calldata) {\\n return bytes(_packet[MESSAGE_OFFSET:]);\\n }\\n\\n function payload(bytes calldata _packet) internal pure returns (bytes calldata) {\\n return bytes(_packet[GUID_OFFSET:]);\\n }\\n\\n function payloadHash(bytes calldata _packet) internal pure returns (bytes32) {\\n return keccak256(payload(_packet));\\n }\\n}\\n\",\"keccak256\":\"0xc84cf1bf785977fe1fbe7566eef902c2db68d0e163813ebe6c34921754802680\",\"license\":\"LZBL-1.2\"},\"@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\n// @dev Import the 'MessagingFee' and 'MessagingReceipt' so it's exposed to OApp implementers\\n// solhint-disable-next-line no-unused-import\\nimport { OAppSender, MessagingFee, MessagingReceipt } from \\\"./OAppSender.sol\\\";\\n// @dev Import the 'Origin' so it's exposed to OApp implementers\\n// solhint-disable-next-line no-unused-import\\nimport { OAppReceiver, Origin } from \\\"./OAppReceiver.sol\\\";\\nimport { OAppCore } from \\\"./OAppCore.sol\\\";\\n\\n/**\\n * @title OApp\\n * @dev Abstract contract serving as the base for OApp implementation, combining OAppSender and OAppReceiver functionality.\\n */\\nabstract contract OApp is OAppSender, OAppReceiver {\\n /**\\n * @dev Constructor to initialize the OApp with the provided endpoint and owner.\\n * @param _endpoint The address of the LOCAL LayerZero endpoint.\\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\\n */\\n constructor(address _endpoint, address _delegate) OAppCore(_endpoint, _delegate) {}\\n\\n /**\\n * @notice Retrieves the OApp version information.\\n * @return senderVersion The version of the OAppSender.sol implementation.\\n * @return receiverVersion The version of the OAppReceiver.sol implementation.\\n */\\n function oAppVersion()\\n public\\n pure\\n virtual\\n override(OAppSender, OAppReceiver)\\n returns (uint64 senderVersion, uint64 receiverVersion)\\n {\\n return (SENDER_VERSION, RECEIVER_VERSION);\\n }\\n}\\n\",\"keccak256\":\"0xac362c4c291fad2f1511a968424b2e78a5ad502d1c867bd31da04be742aca8c5\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/OAppCore.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { Ownable } from \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\nimport { IOAppCore, ILayerZeroEndpointV2 } from \\\"./interfaces/IOAppCore.sol\\\";\\n\\n/**\\n * @title OAppCore\\n * @dev Abstract contract implementing the IOAppCore interface with basic OApp configurations.\\n */\\nabstract contract OAppCore is IOAppCore, Ownable {\\n // The LayerZero endpoint associated with the given OApp\\n ILayerZeroEndpointV2 public immutable endpoint;\\n\\n // Mapping to store peers associated with corresponding endpoints\\n mapping(uint32 eid => bytes32 peer) public peers;\\n\\n /**\\n * @dev Constructor to initialize the OAppCore with the provided endpoint and delegate.\\n * @param _endpoint The address of the LOCAL Layer Zero endpoint.\\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\\n *\\n * @dev The delegate typically should be set as the owner of the contract.\\n */\\n constructor(address _endpoint, address _delegate) {\\n endpoint = ILayerZeroEndpointV2(_endpoint);\\n\\n if (_delegate == address(0)) revert InvalidDelegate();\\n endpoint.setDelegate(_delegate);\\n }\\n\\n /**\\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\\n * @param _eid The endpoint ID.\\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\\n *\\n * @dev Only the owner/admin of the OApp can call this function.\\n * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp.\\n * @dev Set this to bytes32(0) to remove the peer address.\\n * @dev Peer is a bytes32 to accommodate non-evm chains.\\n */\\n function setPeer(uint32 _eid, bytes32 _peer) public virtual onlyOwner {\\n _setPeer(_eid, _peer);\\n }\\n\\n /**\\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\\n * @param _eid The endpoint ID.\\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\\n *\\n * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp.\\n * @dev Set this to bytes32(0) to remove the peer address.\\n * @dev Peer is a bytes32 to accommodate non-evm chains.\\n */\\n function _setPeer(uint32 _eid, bytes32 _peer) internal virtual {\\n peers[_eid] = _peer;\\n emit PeerSet(_eid, _peer);\\n }\\n\\n /**\\n * @notice Internal function to get the peer address associated with a specific endpoint; reverts if NOT set.\\n * ie. the peer is set to bytes32(0).\\n * @param _eid The endpoint ID.\\n * @return peer The address of the peer associated with the specified endpoint.\\n */\\n function _getPeerOrRevert(uint32 _eid) internal view virtual returns (bytes32) {\\n bytes32 peer = peers[_eid];\\n if (peer == bytes32(0)) revert NoPeer(_eid);\\n return peer;\\n }\\n\\n /**\\n * @notice Sets the delegate address for the OApp.\\n * @param _delegate The address of the delegate to be set.\\n *\\n * @dev Only the owner/admin of the OApp can call this function.\\n * @dev Provides the ability for a delegate to set configs, on behalf of the OApp, directly on the Endpoint contract.\\n */\\n function setDelegate(address _delegate) public onlyOwner {\\n endpoint.setDelegate(_delegate);\\n }\\n}\\n\",\"keccak256\":\"0x13a9c2d1d2c1f086b8624f2e84c4a4702212daae36f701d92bb915b535cbe4cc\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/OAppReceiver.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { IOAppReceiver, Origin } from \\\"./interfaces/IOAppReceiver.sol\\\";\\nimport { OAppCore } from \\\"./OAppCore.sol\\\";\\n\\n/**\\n * @title OAppReceiver\\n * @dev Abstract contract implementing the ILayerZeroReceiver interface and extending OAppCore for OApp receivers.\\n */\\nabstract contract OAppReceiver is IOAppReceiver, OAppCore {\\n // Custom error message for when the caller is not the registered endpoint/\\n error OnlyEndpoint(address addr);\\n\\n // @dev The version of the OAppReceiver implementation.\\n // @dev Version is bumped when changes are made to this contract.\\n uint64 internal constant RECEIVER_VERSION = 2;\\n\\n /**\\n * @notice Retrieves the OApp version information.\\n * @return senderVersion The version of the OAppSender.sol contract.\\n * @return receiverVersion The version of the OAppReceiver.sol contract.\\n *\\n * @dev Providing 0 as the default for OAppSender version. Indicates that the OAppSender is not implemented.\\n * ie. this is a RECEIVE only OApp.\\n * @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions.\\n */\\n function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) {\\n return (0, RECEIVER_VERSION);\\n }\\n\\n /**\\n * @notice Indicates whether an address is an approved composeMsg sender to the Endpoint.\\n * @dev _origin The origin information containing the source endpoint and sender address.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address on the src chain.\\n * - nonce: The nonce of the message.\\n * @dev _message The lzReceive payload.\\n * @param _sender The sender address.\\n * @return isSender Is a valid sender.\\n *\\n * @dev Applications can optionally choose to implement separate composeMsg senders that are NOT the bridging layer.\\n * @dev The default sender IS the OAppReceiver implementer.\\n */\\n function isComposeMsgSender(\\n Origin calldata /*_origin*/,\\n bytes calldata /*_message*/,\\n address _sender\\n ) public view virtual returns (bool) {\\n return _sender == address(this);\\n }\\n\\n /**\\n * @notice Checks if the path initialization is allowed based on the provided origin.\\n * @param origin The origin information containing the source endpoint and sender address.\\n * @return Whether the path has been initialized.\\n *\\n * @dev This indicates to the endpoint that the OApp has enabled msgs for this particular path to be received.\\n * @dev This defaults to assuming if a peer has been set, its initialized.\\n * Can be overridden by the OApp if there is other logic to determine this.\\n */\\n function allowInitializePath(Origin calldata origin) public view virtual returns (bool) {\\n return peers[origin.srcEid] == origin.sender;\\n }\\n\\n /**\\n * @notice Retrieves the next nonce for a given source endpoint and sender address.\\n * @dev _srcEid The source endpoint ID.\\n * @dev _sender The sender address.\\n * @return nonce The next nonce.\\n *\\n * @dev The path nonce starts from 1. If 0 is returned it means that there is NO nonce ordered enforcement.\\n * @dev Is required by the off-chain executor to determine the OApp expects msg execution is ordered.\\n * @dev This is also enforced by the OApp.\\n * @dev By default this is NOT enabled. ie. nextNonce is hardcoded to return 0.\\n */\\n function nextNonce(uint32 /*_srcEid*/, bytes32 /*_sender*/) public view virtual returns (uint64 nonce) {\\n return 0;\\n }\\n\\n /**\\n * @dev Entry point for receiving messages or packets from the endpoint.\\n * @param _origin The origin information containing the source endpoint and sender address.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address on the src chain.\\n * - nonce: The nonce of the message.\\n * @param _guid The unique identifier for the received LayerZero message.\\n * @param _message The payload of the received message.\\n * @param _executor The address of the executor for the received message.\\n * @param _extraData Additional arbitrary data provided by the corresponding executor.\\n *\\n * @dev Entry point for receiving msg/packet from the LayerZero endpoint.\\n */\\n function lzReceive(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address _executor,\\n bytes calldata _extraData\\n ) public payable virtual {\\n // Ensures that only the endpoint can attempt to lzReceive() messages to this OApp.\\n if (address(endpoint) != msg.sender) revert OnlyEndpoint(msg.sender);\\n\\n // Ensure that the sender matches the expected peer for the source endpoint.\\n if (_getPeerOrRevert(_origin.srcEid) != _origin.sender) revert OnlyPeer(_origin.srcEid, _origin.sender);\\n\\n // Call the internal OApp implementation of lzReceive.\\n _lzReceive(_origin, _guid, _message, _executor, _extraData);\\n }\\n\\n /**\\n * @dev Internal function to implement lzReceive logic without needing to copy the basic parameter validation.\\n */\\n function _lzReceive(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address _executor,\\n bytes calldata _extraData\\n ) internal virtual;\\n}\\n\",\"keccak256\":\"0x0174e9f1ec4cefe4b5adc26c392269c699b9ff75965364e5b7264426a462c70b\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/OAppSender.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { SafeERC20, IERC20 } from \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\nimport { MessagingParams, MessagingFee, MessagingReceipt } from \\\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\\\";\\nimport { OAppCore } from \\\"./OAppCore.sol\\\";\\n\\n/**\\n * @title OAppSender\\n * @dev Abstract contract implementing the OAppSender functionality for sending messages to a LayerZero endpoint.\\n */\\nabstract contract OAppSender is OAppCore {\\n using SafeERC20 for IERC20;\\n\\n // Custom error messages\\n error NotEnoughNative(uint256 msgValue);\\n error LzTokenUnavailable();\\n\\n // @dev The version of the OAppSender implementation.\\n // @dev Version is bumped when changes are made to this contract.\\n uint64 internal constant SENDER_VERSION = 1;\\n\\n /**\\n * @notice Retrieves the OApp version information.\\n * @return senderVersion The version of the OAppSender.sol contract.\\n * @return receiverVersion The version of the OAppReceiver.sol contract.\\n *\\n * @dev Providing 0 as the default for OAppReceiver version. Indicates that the OAppReceiver is not implemented.\\n * ie. this is a SEND only OApp.\\n * @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions\\n */\\n function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) {\\n return (SENDER_VERSION, 0);\\n }\\n\\n /**\\n * @dev Internal function to interact with the LayerZero EndpointV2.quote() for fee calculation.\\n * @param _dstEid The destination endpoint ID.\\n * @param _message The message payload.\\n * @param _options Additional options for the message.\\n * @param _payInLzToken Flag indicating whether to pay the fee in LZ tokens.\\n * @return fee The calculated MessagingFee for the message.\\n * - nativeFee: The native fee for the message.\\n * - lzTokenFee: The LZ token fee for the message.\\n */\\n function _quote(\\n uint32 _dstEid,\\n bytes memory _message,\\n bytes memory _options,\\n bool _payInLzToken\\n ) internal view virtual returns (MessagingFee memory fee) {\\n return\\n endpoint.quote(\\n MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _payInLzToken),\\n address(this)\\n );\\n }\\n\\n /**\\n * @dev Internal function to interact with the LayerZero EndpointV2.send() for sending a message.\\n * @param _dstEid The destination endpoint ID.\\n * @param _message The message payload.\\n * @param _options Additional options for the message.\\n * @param _fee The calculated LayerZero fee for the message.\\n * - nativeFee: The native fee.\\n * - lzTokenFee: The lzToken fee.\\n * @param _refundAddress The address to receive any excess fee values sent to the endpoint.\\n * @return receipt The receipt for the sent message.\\n * - guid: The unique identifier for the sent message.\\n * - nonce: The nonce of the sent message.\\n * - fee: The LayerZero fee incurred for the message.\\n */\\n function _lzSend(\\n uint32 _dstEid,\\n bytes memory _message,\\n bytes memory _options,\\n MessagingFee memory _fee,\\n address _refundAddress\\n ) internal virtual returns (MessagingReceipt memory receipt) {\\n // @dev Push corresponding fees to the endpoint, any excess is sent back to the _refundAddress from the endpoint.\\n uint256 messageValue = _payNative(_fee.nativeFee);\\n if (_fee.lzTokenFee > 0) _payLzToken(_fee.lzTokenFee);\\n\\n return\\n // solhint-disable-next-line check-send-result\\n endpoint.send{ value: messageValue }(\\n MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _fee.lzTokenFee > 0),\\n _refundAddress\\n );\\n }\\n\\n /**\\n * @dev Internal function to pay the native fee associated with the message.\\n * @param _nativeFee The native fee to be paid.\\n * @return nativeFee The amount of native currency paid.\\n *\\n * @dev If the OApp needs to initiate MULTIPLE LayerZero messages in a single transaction,\\n * this will need to be overridden because msg.value would contain multiple lzFees.\\n * @dev Should be overridden in the event the LayerZero endpoint requires a different native currency.\\n * @dev Some EVMs use an ERC20 as a method for paying transactions/gasFees.\\n * @dev The endpoint is EITHER/OR, ie. it will NOT support both types of native payment at a time.\\n */\\n function _payNative(uint256 _nativeFee) internal virtual returns (uint256 nativeFee) {\\n if (msg.value != _nativeFee) revert NotEnoughNative(msg.value);\\n return _nativeFee;\\n }\\n\\n /**\\n * @dev Internal function to pay the LZ token fee associated with the message.\\n * @param _lzTokenFee The LZ token fee to be paid.\\n *\\n * @dev If the caller is trying to pay in the specified lzToken, then the lzTokenFee is passed to the endpoint.\\n * @dev Any excess sent, is passed back to the specified _refundAddress in the _lzSend().\\n */\\n function _payLzToken(uint256 _lzTokenFee) internal virtual {\\n // @dev Cannot cache the token because it is not immutable in the endpoint.\\n address lzToken = endpoint.lzToken();\\n if (lzToken == address(0)) revert LzTokenUnavailable();\\n\\n // Pay LZ token fee by sending tokens to the endpoint.\\n IERC20(lzToken).safeTransferFrom(msg.sender, address(endpoint), _lzTokenFee);\\n }\\n}\\n\",\"keccak256\":\"0x518cf4adca601923ed4baa6619846a253ea32b8d8775f8bc1faa3dfac7f67c20\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppCore.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { ILayerZeroEndpointV2 } from \\\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\\\";\\n\\n/**\\n * @title IOAppCore\\n */\\ninterface IOAppCore {\\n // Custom error messages\\n error OnlyPeer(uint32 eid, bytes32 sender);\\n error NoPeer(uint32 eid);\\n error InvalidEndpointCall();\\n error InvalidDelegate();\\n\\n // Event emitted when a peer (OApp) is set for a corresponding endpoint\\n event PeerSet(uint32 eid, bytes32 peer);\\n\\n /**\\n * @notice Retrieves the OApp version information.\\n * @return senderVersion The version of the OAppSender.sol contract.\\n * @return receiverVersion The version of the OAppReceiver.sol contract.\\n */\\n function oAppVersion() external view returns (uint64 senderVersion, uint64 receiverVersion);\\n\\n /**\\n * @notice Retrieves the LayerZero endpoint associated with the OApp.\\n * @return iEndpoint The LayerZero endpoint as an interface.\\n */\\n function endpoint() external view returns (ILayerZeroEndpointV2 iEndpoint);\\n\\n /**\\n * @notice Retrieves the peer (OApp) associated with a corresponding endpoint.\\n * @param _eid The endpoint ID.\\n * @return peer The peer address (OApp instance) associated with the corresponding endpoint.\\n */\\n function peers(uint32 _eid) external view returns (bytes32 peer);\\n\\n /**\\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\\n * @param _eid The endpoint ID.\\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\\n */\\n function setPeer(uint32 _eid, bytes32 _peer) external;\\n\\n /**\\n * @notice Sets the delegate address for the OApp Core.\\n * @param _delegate The address of the delegate to be set.\\n */\\n function setDelegate(address _delegate) external;\\n}\\n\",\"keccak256\":\"0x40e49f2de74506e1da5dcaed53a39853f691647f4ceb0fccc8f49a68d3f47c58\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppMsgInspector.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\n/**\\n * @title IOAppMsgInspector\\n * @dev Interface for the OApp Message Inspector, allowing examination of message and options contents.\\n */\\ninterface IOAppMsgInspector {\\n // Custom error message for inspection failure\\n error InspectionFailed(bytes message, bytes options);\\n\\n /**\\n * @notice Allows the inspector to examine LayerZero message contents and optionally throw a revert if invalid.\\n * @param _message The message payload to be inspected.\\n * @param _options Additional options or parameters for inspection.\\n * @return valid A boolean indicating whether the inspection passed (true) or failed (false).\\n *\\n * @dev Optionally done as a revert, OR use the boolean provided to handle the failure.\\n */\\n function inspect(bytes calldata _message, bytes calldata _options) external view returns (bool valid);\\n}\\n\",\"keccak256\":\"0x339654e699043c400cad92de209aa23855ce10211c31cf4114042cc5224d3b7c\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppOptionsType3.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\n/**\\n * @dev Struct representing enforced option parameters.\\n */\\nstruct EnforcedOptionParam {\\n uint32 eid; // Endpoint ID\\n uint16 msgType; // Message Type\\n bytes options; // Additional options\\n}\\n\\n/**\\n * @title IOAppOptionsType3\\n * @dev Interface for the OApp with Type 3 Options, allowing the setting and combining of enforced options.\\n */\\ninterface IOAppOptionsType3 {\\n // Custom error message for invalid options\\n error InvalidOptions(bytes options);\\n\\n // Event emitted when enforced options are set\\n event EnforcedOptionSet(EnforcedOptionParam[] _enforcedOptions);\\n\\n /**\\n * @notice Sets enforced options for specific endpoint and message type combinations.\\n * @param _enforcedOptions An array of EnforcedOptionParam structures specifying enforced options.\\n */\\n function setEnforcedOptions(EnforcedOptionParam[] calldata _enforcedOptions) external;\\n\\n /**\\n * @notice Combines options for a given endpoint and message type.\\n * @param _eid The endpoint ID.\\n * @param _msgType The OApp message type.\\n * @param _extraOptions Additional options passed by the caller.\\n * @return options The combination of caller specified options AND enforced options.\\n */\\n function combineOptions(\\n uint32 _eid,\\n uint16 _msgType,\\n bytes calldata _extraOptions\\n ) external view returns (bytes memory options);\\n}\\n\",\"keccak256\":\"0x9fc08a51e9d7c9c710c4eb26f84fe77228305ad7da63fa486ff24ebf2f3bc461\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppReceiver.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.20;\\n\\nimport { ILayerZeroReceiver, Origin } from \\\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroReceiver.sol\\\";\\n\\ninterface IOAppReceiver is ILayerZeroReceiver {\\n /**\\n * @notice Indicates whether an address is an approved composeMsg sender to the Endpoint.\\n * @param _origin The origin information containing the source endpoint and sender address.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address on the src chain.\\n * - nonce: The nonce of the message.\\n * @param _message The lzReceive payload.\\n * @param _sender The sender address.\\n * @return isSender Is a valid sender.\\n *\\n * @dev Applications can optionally choose to implement a separate composeMsg sender that is NOT the bridging layer.\\n * @dev The default sender IS the OAppReceiver implementer.\\n */\\n function isComposeMsgSender(\\n Origin calldata _origin,\\n bytes calldata _message,\\n address _sender\\n ) external view returns (bool isSender);\\n}\\n\",\"keccak256\":\"0xd26135185e19b3732746d4a9e2923e896f28dec8664bab161faea2ee26fcdc3d\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { Ownable } from \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\nimport { IOAppOptionsType3, EnforcedOptionParam } from \\\"../interfaces/IOAppOptionsType3.sol\\\";\\n\\n/**\\n * @title OAppOptionsType3\\n * @dev Abstract contract implementing the IOAppOptionsType3 interface with type 3 options.\\n */\\nabstract contract OAppOptionsType3 is IOAppOptionsType3, Ownable {\\n uint16 internal constant OPTION_TYPE_3 = 3;\\n\\n // @dev The \\\"msgType\\\" should be defined in the child contract.\\n mapping(uint32 eid => mapping(uint16 msgType => bytes enforcedOption)) public enforcedOptions;\\n\\n /**\\n * @dev Sets the enforced options for specific endpoint and message type combinations.\\n * @param _enforcedOptions An array of EnforcedOptionParam structures specifying enforced options.\\n *\\n * @dev Only the owner/admin of the OApp can call this function.\\n * @dev Provides a way for the OApp to enforce things like paying for PreCrime, AND/OR minimum dst lzReceive gas amounts etc.\\n * @dev These enforced options can vary as the potential options/execution on the remote may differ as per the msgType.\\n * eg. Amount of lzReceive() gas necessary to deliver a lzCompose() message adds overhead you dont want to pay\\n * if you are only making a standard LayerZero message ie. lzReceive() WITHOUT sendCompose().\\n */\\n function setEnforcedOptions(EnforcedOptionParam[] calldata _enforcedOptions) public virtual onlyOwner {\\n _setEnforcedOptions(_enforcedOptions);\\n }\\n\\n /**\\n * @dev Sets the enforced options for specific endpoint and message type combinations.\\n * @param _enforcedOptions An array of EnforcedOptionParam structures specifying enforced options.\\n *\\n * @dev Provides a way for the OApp to enforce things like paying for PreCrime, AND/OR minimum dst lzReceive gas amounts etc.\\n * @dev These enforced options can vary as the potential options/execution on the remote may differ as per the msgType.\\n * eg. Amount of lzReceive() gas necessary to deliver a lzCompose() message adds overhead you dont want to pay\\n * if you are only making a standard LayerZero message ie. lzReceive() WITHOUT sendCompose().\\n */\\n function _setEnforcedOptions(EnforcedOptionParam[] memory _enforcedOptions) internal virtual {\\n for (uint256 i = 0; i < _enforcedOptions.length; i++) {\\n // @dev Enforced options are only available for optionType 3, as type 1 and 2 dont support combining.\\n _assertOptionsType3(_enforcedOptions[i].options);\\n enforcedOptions[_enforcedOptions[i].eid][_enforcedOptions[i].msgType] = _enforcedOptions[i].options;\\n }\\n\\n emit EnforcedOptionSet(_enforcedOptions);\\n }\\n\\n /**\\n * @notice Combines options for a given endpoint and message type.\\n * @param _eid The endpoint ID.\\n * @param _msgType The OAPP message type.\\n * @param _extraOptions Additional options passed by the caller.\\n * @return options The combination of caller specified options AND enforced options.\\n *\\n * @dev If there is an enforced lzReceive option:\\n * - {gasLimit: 200k, msg.value: 1 ether} AND a caller supplies a lzReceive option: {gasLimit: 100k, msg.value: 0.5 ether}\\n * - The resulting options will be {gasLimit: 300k, msg.value: 1.5 ether} when the message is executed on the remote lzReceive() function.\\n * @dev This presence of duplicated options is handled off-chain in the verifier/executor.\\n */\\n function combineOptions(\\n uint32 _eid,\\n uint16 _msgType,\\n bytes calldata _extraOptions\\n ) public view virtual returns (bytes memory) {\\n bytes memory enforced = enforcedOptions[_eid][_msgType];\\n\\n // No enforced options, pass whatever the caller supplied, even if it's empty or legacy type 1/2 options.\\n if (enforced.length == 0) return _extraOptions;\\n\\n // No caller options, return enforced\\n if (_extraOptions.length == 0) return enforced;\\n\\n // @dev If caller provided _extraOptions, must be type 3 as its the ONLY type that can be combined.\\n if (_extraOptions.length >= 2) {\\n _assertOptionsType3(_extraOptions);\\n // @dev Remove the first 2 bytes containing the type from the _extraOptions and combine with enforced.\\n return bytes.concat(enforced, _extraOptions[2:]);\\n }\\n\\n // No valid set of options was found.\\n revert InvalidOptions(_extraOptions);\\n }\\n\\n /**\\n * @dev Internal function to assert that options are of type 3.\\n * @param _options The options to be checked.\\n */\\n function _assertOptionsType3(bytes memory _options) internal pure virtual {\\n uint16 optionsType;\\n assembly {\\n optionsType := mload(add(_options, 2))\\n }\\n if (optionsType != OPTION_TYPE_3) revert InvalidOptions(_options);\\n }\\n}\\n\",\"keccak256\":\"0x5275636cd47e660a2fdf6c7fe9d41ff3cc866b785cc8a9d88c1b8ca983509f01\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/precrime/OAppPreCrimeSimulator.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { Ownable } from \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\nimport { IPreCrime } from \\\"./interfaces/IPreCrime.sol\\\";\\nimport { IOAppPreCrimeSimulator, InboundPacket, Origin } from \\\"./interfaces/IOAppPreCrimeSimulator.sol\\\";\\n\\n/**\\n * @title OAppPreCrimeSimulator\\n * @dev Abstract contract serving as the base for preCrime simulation functionality in an OApp.\\n */\\nabstract contract OAppPreCrimeSimulator is IOAppPreCrimeSimulator, Ownable {\\n // The address of the preCrime implementation.\\n address public preCrime;\\n\\n /**\\n * @dev Retrieves the address of the OApp contract.\\n * @return The address of the OApp contract.\\n *\\n * @dev The simulator contract is the base contract for the OApp by default.\\n * @dev If the simulator is a separate contract, override this function.\\n */\\n function oApp() external view virtual returns (address) {\\n return address(this);\\n }\\n\\n /**\\n * @dev Sets the preCrime contract address.\\n * @param _preCrime The address of the preCrime contract.\\n */\\n function setPreCrime(address _preCrime) public virtual onlyOwner {\\n preCrime = _preCrime;\\n emit PreCrimeSet(_preCrime);\\n }\\n\\n /**\\n * @dev Interface for pre-crime simulations. Always reverts at the end with the simulation results.\\n * @param _packets An array of InboundPacket objects representing received packets to be delivered.\\n *\\n * @dev WARNING: MUST revert at the end with the simulation results.\\n * @dev Gives the preCrime implementation the ability to mock sending packets to the lzReceive function,\\n * WITHOUT actually executing them.\\n */\\n function lzReceiveAndRevert(InboundPacket[] calldata _packets) public payable virtual {\\n for (uint256 i = 0; i < _packets.length; i++) {\\n InboundPacket calldata packet = _packets[i];\\n\\n // Ignore packets that are not from trusted peers.\\n if (!isPeer(packet.origin.srcEid, packet.origin.sender)) continue;\\n\\n // @dev Because a verifier is calling this function, it doesnt have access to executor params:\\n // - address _executor\\n // - bytes calldata _extraData\\n // preCrime will NOT work for OApps that rely on these two parameters inside of their _lzReceive().\\n // They are instead stubbed to default values, address(0) and bytes(\\\"\\\")\\n // @dev Calling this.lzReceiveSimulate removes ability for assembly return 0 callstack exit,\\n // which would cause the revert to be ignored.\\n this.lzReceiveSimulate{ value: packet.value }(\\n packet.origin,\\n packet.guid,\\n packet.message,\\n packet.executor,\\n packet.extraData\\n );\\n }\\n\\n // @dev Revert with the simulation results. msg.sender must implement IPreCrime.buildSimulationResult().\\n revert SimulationResult(IPreCrime(msg.sender).buildSimulationResult());\\n }\\n\\n /**\\n * @dev Is effectively an internal function because msg.sender must be address(this).\\n * Allows resetting the call stack for 'internal' calls.\\n * @param _origin The origin information containing the source endpoint and sender address.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address on the src chain.\\n * - nonce: The nonce of the message.\\n * @param _guid The unique identifier of the packet.\\n * @param _message The message payload of the packet.\\n * @param _executor The executor address for the packet.\\n * @param _extraData Additional data for the packet.\\n */\\n function lzReceiveSimulate(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address _executor,\\n bytes calldata _extraData\\n ) external payable virtual {\\n // @dev Ensure ONLY can be called 'internally'.\\n if (msg.sender != address(this)) revert OnlySelf();\\n _lzReceiveSimulate(_origin, _guid, _message, _executor, _extraData);\\n }\\n\\n /**\\n * @dev Internal function to handle the OAppPreCrimeSimulator simulated receive.\\n * @param _origin The origin information.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address from the src chain.\\n * - nonce: The nonce of the LayerZero message.\\n * @param _guid The GUID of the LayerZero message.\\n * @param _message The LayerZero message.\\n * @param _executor The address of the off-chain executor.\\n * @param _extraData Arbitrary data passed by the msg executor.\\n *\\n * @dev Enables the preCrime simulator to mock sending lzReceive() messages,\\n * routes the msg down from the OAppPreCrimeSimulator, and back up to the OAppReceiver.\\n */\\n function _lzReceiveSimulate(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address _executor,\\n bytes calldata _extraData\\n ) internal virtual;\\n\\n /**\\n * @dev checks if the specified peer is considered 'trusted' by the OApp.\\n * @param _eid The endpoint Id to check.\\n * @param _peer The peer to check.\\n * @return Whether the peer passed is considered 'trusted' by the OApp.\\n */\\n function isPeer(uint32 _eid, bytes32 _peer) public view virtual returns (bool);\\n}\\n\",\"keccak256\":\"0x205a0abfd8b3c9af2740769f251381b84999b8e9347f3cd50de3ef8290a17750\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/precrime/interfaces/IOAppPreCrimeSimulator.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\n// @dev Import the Origin so it's exposed to OAppPreCrimeSimulator implementers.\\n// solhint-disable-next-line no-unused-import\\nimport { InboundPacket, Origin } from \\\"../libs/Packet.sol\\\";\\n\\n/**\\n * @title IOAppPreCrimeSimulator Interface\\n * @dev Interface for the preCrime simulation functionality in an OApp.\\n */\\ninterface IOAppPreCrimeSimulator {\\n // @dev simulation result used in PreCrime implementation\\n error SimulationResult(bytes result);\\n error OnlySelf();\\n\\n /**\\n * @dev Emitted when the preCrime contract address is set.\\n * @param preCrimeAddress The address of the preCrime contract.\\n */\\n event PreCrimeSet(address preCrimeAddress);\\n\\n /**\\n * @dev Retrieves the address of the preCrime contract implementation.\\n * @return The address of the preCrime contract.\\n */\\n function preCrime() external view returns (address);\\n\\n /**\\n * @dev Retrieves the address of the OApp contract.\\n * @return The address of the OApp contract.\\n */\\n function oApp() external view returns (address);\\n\\n /**\\n * @dev Sets the preCrime contract address.\\n * @param _preCrime The address of the preCrime contract.\\n */\\n function setPreCrime(address _preCrime) external;\\n\\n /**\\n * @dev Mocks receiving a packet, then reverts with a series of data to infer the state/result.\\n * @param _packets An array of LayerZero InboundPacket objects representing received packets.\\n */\\n function lzReceiveAndRevert(InboundPacket[] calldata _packets) external payable;\\n\\n /**\\n * @dev checks if the specified peer is considered 'trusted' by the OApp.\\n * @param _eid The endpoint Id to check.\\n * @param _peer The peer to check.\\n * @return Whether the peer passed is considered 'trusted' by the OApp.\\n */\\n function isPeer(uint32 _eid, bytes32 _peer) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x5d24db150949ea8e6437178e65a942e8c8b7f332e5daf32750f56b23b35b5bb2\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/precrime/interfaces/IPreCrime.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\nstruct PreCrimePeer {\\n uint32 eid;\\n bytes32 preCrime;\\n bytes32 oApp;\\n}\\n\\n// TODO not done yet\\ninterface IPreCrime {\\n error OnlyOffChain();\\n\\n // for simulate()\\n error PacketOversize(uint256 max, uint256 actual);\\n error PacketUnsorted();\\n error SimulationFailed(bytes reason);\\n\\n // for preCrime()\\n error SimulationResultNotFound(uint32 eid);\\n error InvalidSimulationResult(uint32 eid, bytes reason);\\n error CrimeFound(bytes crime);\\n\\n function getConfig(bytes[] calldata _packets, uint256[] calldata _packetMsgValues) external returns (bytes memory);\\n\\n function simulate(\\n bytes[] calldata _packets,\\n uint256[] calldata _packetMsgValues\\n ) external payable returns (bytes memory);\\n\\n function buildSimulationResult() external view returns (bytes memory);\\n\\n function preCrime(\\n bytes[] calldata _packets,\\n uint256[] calldata _packetMsgValues,\\n bytes[] calldata _simulations\\n ) external;\\n\\n function version() external view returns (uint64 major, uint8 minor);\\n}\\n\",\"keccak256\":\"0xc8d869f27ef8ceb2e13fdf6a70682fd4dee3f90c4924eb8e125bc1e66cb6af84\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/precrime/libs/Packet.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { Origin } from \\\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\\\";\\nimport { PacketV1Codec } from \\\"@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.sol\\\";\\n\\n/**\\n * @title InboundPacket\\n * @dev Structure representing an inbound packet received by the contract.\\n */\\nstruct InboundPacket {\\n Origin origin; // Origin information of the packet.\\n uint32 dstEid; // Destination endpointId of the packet.\\n address receiver; // Receiver address for the packet.\\n bytes32 guid; // Unique identifier of the packet.\\n uint256 value; // msg.value of the packet.\\n address executor; // Executor address for the packet.\\n bytes message; // Message payload of the packet.\\n bytes extraData; // Additional arbitrary data for the packet.\\n}\\n\\n/**\\n * @title PacketDecoder\\n * @dev Library for decoding LayerZero packets.\\n */\\nlibrary PacketDecoder {\\n using PacketV1Codec for bytes;\\n\\n /**\\n * @dev Decode an inbound packet from the given packet data.\\n * @param _packet The packet data to decode.\\n * @return packet An InboundPacket struct representing the decoded packet.\\n */\\n function decode(bytes calldata _packet) internal pure returns (InboundPacket memory packet) {\\n packet.origin = Origin(_packet.srcEid(), _packet.sender(), _packet.nonce());\\n packet.dstEid = _packet.dstEid();\\n packet.receiver = _packet.receiverB20();\\n packet.guid = _packet.guid();\\n packet.message = _packet.message();\\n }\\n\\n /**\\n * @dev Decode multiple inbound packets from the given packet data and associated message values.\\n * @param _packets An array of packet data to decode.\\n * @param _packetMsgValues An array of associated message values for each packet.\\n * @return packets An array of InboundPacket structs representing the decoded packets.\\n */\\n function decode(\\n bytes[] calldata _packets,\\n uint256[] memory _packetMsgValues\\n ) internal pure returns (InboundPacket[] memory packets) {\\n packets = new InboundPacket[](_packets.length);\\n for (uint256 i = 0; i < _packets.length; i++) {\\n bytes calldata packet = _packets[i];\\n packets[i] = PacketDecoder.decode(packet);\\n // @dev Allows the verifier to specify the msg.value that gets passed in lzReceive.\\n packets[i].value = _packetMsgValues[i];\\n }\\n }\\n}\\n\",\"keccak256\":\"0xcb2fb1c5b2eb3731de78b479b9c2ab3bba326fe0b0b3a008590f18e881e457a6\",\"license\":\"MIT\"},\"@layerzerolabs/oft-evm/contracts/OFT.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { ERC20 } from \\\"@openzeppelin/contracts/token/ERC20/ERC20.sol\\\";\\nimport { IOFT, OFTCore } from \\\"./OFTCore.sol\\\";\\n\\n/**\\n * @title OFT Contract\\n * @dev OFT is an ERC-20 token that extends the functionality of the OFTCore contract.\\n */\\nabstract contract OFT is OFTCore, ERC20 {\\n /**\\n * @dev Constructor for the OFT contract.\\n * @param _name The name of the OFT.\\n * @param _symbol The symbol of the OFT.\\n * @param _lzEndpoint The LayerZero endpoint address.\\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\\n */\\n constructor(\\n string memory _name,\\n string memory _symbol,\\n address _lzEndpoint,\\n address _delegate\\n ) ERC20(_name, _symbol) OFTCore(decimals(), _lzEndpoint, _delegate) {}\\n\\n /**\\n * @dev Retrieves the address of the underlying ERC20 implementation.\\n * @return The address of the OFT token.\\n *\\n * @dev In the case of OFT, address(this) and erc20 are the same contract.\\n */\\n function token() public view returns (address) {\\n return address(this);\\n }\\n\\n /**\\n * @notice Indicates whether the OFT contract requires approval of the 'token()' to send.\\n * @return requiresApproval Needs approval of the underlying token implementation.\\n *\\n * @dev In the case of OFT where the contract IS the token, approval is NOT required.\\n */\\n function approvalRequired() external pure virtual returns (bool) {\\n return false;\\n }\\n\\n /**\\n * @dev Burns tokens from the sender's specified balance.\\n * @param _from The address to debit the tokens from.\\n * @param _amountLD The amount of tokens to send in local decimals.\\n * @param _minAmountLD The minimum amount to send in local decimals.\\n * @param _dstEid The destination chain ID.\\n * @return amountSentLD The amount sent in local decimals.\\n * @return amountReceivedLD The amount received in local decimals on the remote.\\n */\\n function _debit(\\n address _from,\\n uint256 _amountLD,\\n uint256 _minAmountLD,\\n uint32 _dstEid\\n ) internal virtual override returns (uint256 amountSentLD, uint256 amountReceivedLD) {\\n (amountSentLD, amountReceivedLD) = _debitView(_amountLD, _minAmountLD, _dstEid);\\n\\n // @dev In NON-default OFT, amountSentLD could be 100, with a 10% fee, the amountReceivedLD amount is 90,\\n // therefore amountSentLD CAN differ from amountReceivedLD.\\n\\n // @dev Default OFT burns on src.\\n _burn(_from, amountSentLD);\\n }\\n\\n /**\\n * @dev Credits tokens to the specified address.\\n * @param _to The address to credit the tokens to.\\n * @param _amountLD The amount of tokens to credit in local decimals.\\n * @dev _srcEid The source chain ID.\\n * @return amountReceivedLD The amount of tokens ACTUALLY received in local decimals.\\n */\\n function _credit(\\n address _to,\\n uint256 _amountLD,\\n uint32 /*_srcEid*/\\n ) internal virtual override returns (uint256 amountReceivedLD) {\\n if (_to == address(0x0)) _to = address(0xdead); // _mint(...) does not support address(0x0)\\n // @dev Default OFT mints on dst.\\n _mint(_to, _amountLD);\\n // @dev In the case of NON-default OFT, the _amountLD MIGHT not be == amountReceivedLD.\\n return _amountLD;\\n }\\n}\\n\",\"keccak256\":\"0xdc3582e4a20e02a79050c17058a1f1f42a4335d1a70be06c0a52a3fb05d4c89a\",\"license\":\"MIT\"},\"@layerzerolabs/oft-evm/contracts/OFTCore.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { IERC20 } from \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\n\\nimport { OApp, Origin } from \\\"@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol\\\";\\nimport { OAppOptionsType3 } from \\\"@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol\\\";\\nimport { IOAppMsgInspector } from \\\"@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppMsgInspector.sol\\\";\\n\\nimport { OAppPreCrimeSimulator } from \\\"@layerzerolabs/oapp-evm/contracts/precrime/OAppPreCrimeSimulator.sol\\\";\\n\\nimport { IOFT, SendParam, OFTLimit, OFTReceipt, OFTFeeDetail, MessagingReceipt, MessagingFee } from \\\"./interfaces/IOFT.sol\\\";\\nimport { OFTMsgCodec } from \\\"./libs/OFTMsgCodec.sol\\\";\\nimport { OFTComposeMsgCodec } from \\\"./libs/OFTComposeMsgCodec.sol\\\";\\n\\n/**\\n * @title OFTCore\\n * @dev Abstract contract for the OftChain (OFT) token.\\n */\\nabstract contract OFTCore is IOFT, OApp, OAppPreCrimeSimulator, OAppOptionsType3 {\\n using OFTMsgCodec for bytes;\\n using OFTMsgCodec for bytes32;\\n\\n // @notice Provides a conversion rate when swapping between denominations of SD and LD\\n // - shareDecimals == SD == shared Decimals\\n // - localDecimals == LD == local decimals\\n // @dev Considers that tokens have different decimal amounts on various chains.\\n // @dev eg.\\n // For a token\\n // - locally with 4 decimals --> 1.2345 => uint(12345)\\n // - remotely with 2 decimals --> 1.23 => uint(123)\\n // - The conversion rate would be 10 ** (4 - 2) = 100\\n // @dev If you want to send 1.2345 -> (uint 12345), you CANNOT represent that value on the remote,\\n // you can only display 1.23 -> uint(123).\\n // @dev To preserve the dust that would otherwise be lost on that conversion,\\n // we need to unify a denomination that can be represented on ALL chains inside of the OFT mesh\\n uint256 public immutable decimalConversionRate;\\n\\n // @notice Msg types that are used to identify the various OFT operations.\\n // @dev This can be extended in child contracts for non-default oft operations\\n // @dev These values are used in things like combineOptions() in OAppOptionsType3.sol.\\n uint16 public constant SEND = 1;\\n uint16 public constant SEND_AND_CALL = 2;\\n\\n // Address of an optional contract to inspect both 'message' and 'options'\\n address public msgInspector;\\n event MsgInspectorSet(address inspector);\\n\\n /**\\n * @dev Constructor.\\n * @param _localDecimals The decimals of the token on the local chain (this chain).\\n * @param _endpoint The address of the LayerZero endpoint.\\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\\n */\\n constructor(uint8 _localDecimals, address _endpoint, address _delegate) OApp(_endpoint, _delegate) {\\n if (_localDecimals < sharedDecimals()) revert InvalidLocalDecimals();\\n decimalConversionRate = 10 ** (_localDecimals - sharedDecimals());\\n }\\n\\n /**\\n * @notice Retrieves interfaceID and the version of the OFT.\\n * @return interfaceId The interface ID.\\n * @return version The version.\\n *\\n * @dev interfaceId: This specific interface ID is '0x02e49c2c'.\\n * @dev version: Indicates a cross-chain compatible msg encoding with other OFTs.\\n * @dev If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented.\\n * ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)\\n */\\n function oftVersion() external pure virtual returns (bytes4 interfaceId, uint64 version) {\\n return (type(IOFT).interfaceId, 1);\\n }\\n\\n /**\\n * @dev Retrieves the shared decimals of the OFT.\\n * @return The shared decimals of the OFT.\\n *\\n * @dev Sets an implicit cap on the amount of tokens, over uint64.max() will need some sort of outbound cap / totalSupply cap\\n * Lowest common decimal denominator between chains.\\n * Defaults to 6 decimal places to provide up to 18,446,744,073,709.551615 units (max uint64).\\n * For tokens exceeding this totalSupply(), they will need to override the sharedDecimals function with something smaller.\\n * ie. 4 sharedDecimals would be 1,844,674,407,370,955.1615\\n */\\n function sharedDecimals() public view virtual returns (uint8) {\\n return 6;\\n }\\n\\n /**\\n * @dev Sets the message inspector address for the OFT.\\n * @param _msgInspector The address of the message inspector.\\n *\\n * @dev This is an optional contract that can be used to inspect both 'message' and 'options'.\\n * @dev Set it to address(0) to disable it, or set it to a contract address to enable it.\\n */\\n function setMsgInspector(address _msgInspector) public virtual onlyOwner {\\n msgInspector = _msgInspector;\\n emit MsgInspectorSet(_msgInspector);\\n }\\n\\n /**\\n * @notice Provides the fee breakdown and settings data for an OFT. Unused in the default implementation.\\n * @param _sendParam The parameters for the send operation.\\n * @return oftLimit The OFT limit information.\\n * @return oftFeeDetails The details of OFT fees.\\n * @return oftReceipt The OFT receipt information.\\n */\\n function quoteOFT(\\n SendParam calldata _sendParam\\n )\\n external\\n view\\n virtual\\n returns (OFTLimit memory oftLimit, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory oftReceipt)\\n {\\n uint256 minAmountLD = 0; // Unused in the default implementation.\\n uint256 maxAmountLD = IERC20(this.token()).totalSupply(); // Unused in the default implementation.\\n oftLimit = OFTLimit(minAmountLD, maxAmountLD);\\n\\n // Unused in the default implementation; reserved for future complex fee details.\\n oftFeeDetails = new OFTFeeDetail[](0);\\n\\n // @dev This is the same as the send() operation, but without the actual send.\\n // - amountSentLD is the amount in local decimals that would be sent from the sender.\\n // - amountReceivedLD is the amount in local decimals that will be credited to the recipient on the remote OFT instance.\\n // @dev The amountSentLD MIGHT not equal the amount the user actually receives. HOWEVER, the default does.\\n (uint256 amountSentLD, uint256 amountReceivedLD) = _debitView(\\n _sendParam.amountLD,\\n _sendParam.minAmountLD,\\n _sendParam.dstEid\\n );\\n oftReceipt = OFTReceipt(amountSentLD, amountReceivedLD);\\n }\\n\\n /**\\n * @notice Provides a quote for the send() operation.\\n * @param _sendParam The parameters for the send() operation.\\n * @param _payInLzToken Flag indicating whether the caller is paying in the LZ token.\\n * @return msgFee The calculated LayerZero messaging fee from the send() operation.\\n *\\n * @dev MessagingFee: LayerZero msg fee\\n * - nativeFee: The native fee.\\n * - lzTokenFee: The lzToken fee.\\n */\\n function quoteSend(\\n SendParam calldata _sendParam,\\n bool _payInLzToken\\n ) external view virtual returns (MessagingFee memory msgFee) {\\n // @dev mock the amount to receive, this is the same operation used in the send().\\n // The quote is as similar as possible to the actual send() operation.\\n (, uint256 amountReceivedLD) = _debitView(_sendParam.amountLD, _sendParam.minAmountLD, _sendParam.dstEid);\\n\\n // @dev Builds the options and OFT message to quote in the endpoint.\\n (bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam, amountReceivedLD);\\n\\n // @dev Calculates the LayerZero fee for the send() operation.\\n return _quote(_sendParam.dstEid, message, options, _payInLzToken);\\n }\\n\\n /**\\n * @dev Executes the send operation.\\n * @param _sendParam The parameters for the send operation.\\n * @param _fee The calculated fee for the send() operation.\\n * - nativeFee: The native fee.\\n * - lzTokenFee: The lzToken fee.\\n * @param _refundAddress The address to receive any excess funds.\\n * @return msgReceipt The receipt for the send operation.\\n * @return oftReceipt The OFT receipt information.\\n *\\n * @dev MessagingReceipt: LayerZero msg receipt\\n * - guid: The unique identifier for the sent message.\\n * - nonce: The nonce of the sent message.\\n * - fee: The LayerZero fee incurred for the message.\\n */\\n function send(\\n SendParam calldata _sendParam,\\n MessagingFee calldata _fee,\\n address _refundAddress\\n ) external payable virtual returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) {\\n return _send(_sendParam, _fee, _refundAddress);\\n }\\n\\n /**\\n * @dev Internal function to execute the send operation.\\n * @param _sendParam The parameters for the send operation.\\n * @param _fee The calculated fee for the send() operation.\\n * - nativeFee: The native fee.\\n * - lzTokenFee: The lzToken fee.\\n * @param _refundAddress The address to receive any excess funds.\\n * @return msgReceipt The receipt for the send operation.\\n * @return oftReceipt The OFT receipt information.\\n *\\n * @dev MessagingReceipt: LayerZero msg receipt\\n * - guid: The unique identifier for the sent message.\\n * - nonce: The nonce of the sent message.\\n * - fee: The LayerZero fee incurred for the message.\\n */\\n function _send(\\n SendParam calldata _sendParam,\\n MessagingFee calldata _fee,\\n address _refundAddress\\n ) internal virtual returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) {\\n // @dev Applies the token transfers regarding this send() operation.\\n // - amountSentLD is the amount in local decimals that was ACTUALLY sent/debited from the sender.\\n // - amountReceivedLD is the amount in local decimals that will be received/credited to the recipient on the remote OFT instance.\\n (uint256 amountSentLD, uint256 amountReceivedLD) = _debit(\\n msg.sender,\\n _sendParam.amountLD,\\n _sendParam.minAmountLD,\\n _sendParam.dstEid\\n );\\n\\n // @dev Builds the options and OFT message to quote in the endpoint.\\n (bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam, amountReceivedLD);\\n\\n // @dev Sends the message to the LayerZero endpoint and returns the LayerZero msg receipt.\\n msgReceipt = _lzSend(_sendParam.dstEid, message, options, _fee, _refundAddress);\\n // @dev Formulate the OFT receipt.\\n oftReceipt = OFTReceipt(amountSentLD, amountReceivedLD);\\n\\n emit OFTSent(msgReceipt.guid, _sendParam.dstEid, msg.sender, amountSentLD, amountReceivedLD);\\n }\\n\\n /**\\n * @dev Internal function to build the message and options.\\n * @param _sendParam The parameters for the send() operation.\\n * @param _amountLD The amount in local decimals.\\n * @return message The encoded message.\\n * @return options The encoded options.\\n */\\n function _buildMsgAndOptions(\\n SendParam calldata _sendParam,\\n uint256 _amountLD\\n ) internal view virtual returns (bytes memory message, bytes memory options) {\\n bool hasCompose;\\n // @dev This generated message has the msg.sender encoded into the payload so the remote knows who the caller is.\\n (message, hasCompose) = OFTMsgCodec.encode(\\n _sendParam.to,\\n _toSD(_amountLD),\\n // @dev Must be include a non empty bytes if you want to compose, EVEN if you dont need it on the remote.\\n // EVEN if you dont require an arbitrary payload to be sent... eg. '0x01'\\n _sendParam.composeMsg\\n );\\n // @dev Change the msg type depending if its composed or not.\\n uint16 msgType = hasCompose ? SEND_AND_CALL : SEND;\\n // @dev Combine the callers _extraOptions with the enforced options via the OAppOptionsType3.\\n options = combineOptions(_sendParam.dstEid, msgType, _sendParam.extraOptions);\\n\\n // @dev Optionally inspect the message and options depending if the OApp owner has set a msg inspector.\\n // @dev If it fails inspection, needs to revert in the implementation. ie. does not rely on return boolean\\n address inspector = msgInspector; // caches the msgInspector to avoid potential double storage read\\n if (inspector != address(0)) IOAppMsgInspector(inspector).inspect(message, options);\\n }\\n\\n /**\\n * @dev Internal function to handle the receive on the LayerZero endpoint.\\n * @param _origin The origin information.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address from the src chain.\\n * - nonce: The nonce of the LayerZero message.\\n * @param _guid The unique identifier for the received LayerZero message.\\n * @param _message The encoded message.\\n * @dev _executor The address of the executor.\\n * @dev _extraData Additional data.\\n */\\n function _lzReceive(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address /*_executor*/, // @dev unused in the default implementation.\\n bytes calldata /*_extraData*/ // @dev unused in the default implementation.\\n ) internal virtual override {\\n // @dev The src sending chain doesnt know the address length on this chain (potentially non-evm)\\n // Thus everything is bytes32() encoded in flight.\\n address toAddress = _message.sendTo().bytes32ToAddress();\\n // @dev Credit the amountLD to the recipient and return the ACTUAL amount the recipient received in local decimals\\n uint256 amountReceivedLD = _credit(toAddress, _toLD(_message.amountSD()), _origin.srcEid);\\n\\n if (_message.isComposed()) {\\n // @dev Proprietary composeMsg format for the OFT.\\n bytes memory composeMsg = OFTComposeMsgCodec.encode(\\n _origin.nonce,\\n _origin.srcEid,\\n amountReceivedLD,\\n _message.composeMsg()\\n );\\n\\n // @dev Stores the lzCompose payload that will be executed in a separate tx.\\n // Standardizes functionality for executing arbitrary contract invocation on some non-evm chains.\\n // @dev The off-chain executor will listen and process the msg based on the src-chain-callers compose options passed.\\n // @dev The index is used when a OApp needs to compose multiple msgs on lzReceive.\\n // For default OFT implementation there is only 1 compose msg per lzReceive, thus its always 0.\\n endpoint.sendCompose(toAddress, _guid, 0 /* the index of the composed message*/, composeMsg);\\n }\\n\\n emit OFTReceived(_guid, _origin.srcEid, toAddress, amountReceivedLD);\\n }\\n\\n /**\\n * @dev Internal function to handle the OAppPreCrimeSimulator simulated receive.\\n * @param _origin The origin information.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address from the src chain.\\n * - nonce: The nonce of the LayerZero message.\\n * @param _guid The unique identifier for the received LayerZero message.\\n * @param _message The LayerZero message.\\n * @param _executor The address of the off-chain executor.\\n * @param _extraData Arbitrary data passed by the msg executor.\\n *\\n * @dev Enables the preCrime simulator to mock sending lzReceive() messages,\\n * routes the msg down from the OAppPreCrimeSimulator, and back up to the OAppReceiver.\\n */\\n function _lzReceiveSimulate(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address _executor,\\n bytes calldata _extraData\\n ) internal virtual override {\\n _lzReceive(_origin, _guid, _message, _executor, _extraData);\\n }\\n\\n /**\\n * @dev Check if the peer is considered 'trusted' by the OApp.\\n * @param _eid The endpoint ID to check.\\n * @param _peer The peer to check.\\n * @return Whether the peer passed is considered 'trusted' by the OApp.\\n *\\n * @dev Enables OAppPreCrimeSimulator to check whether a potential Inbound Packet is from a trusted source.\\n */\\n function isPeer(uint32 _eid, bytes32 _peer) public view virtual override returns (bool) {\\n return peers[_eid] == _peer;\\n }\\n\\n /**\\n * @dev Internal function to remove dust from the given local decimal amount.\\n * @param _amountLD The amount in local decimals.\\n * @return amountLD The amount after removing dust.\\n *\\n * @dev Prevents the loss of dust when moving amounts between chains with different decimals.\\n * @dev eg. uint(123) with a conversion rate of 100 becomes uint(100).\\n */\\n function _removeDust(uint256 _amountLD) internal view virtual returns (uint256 amountLD) {\\n return (_amountLD / decimalConversionRate) * decimalConversionRate;\\n }\\n\\n /**\\n * @dev Internal function to convert an amount from shared decimals into local decimals.\\n * @param _amountSD The amount in shared decimals.\\n * @return amountLD The amount in local decimals.\\n */\\n function _toLD(uint64 _amountSD) internal view virtual returns (uint256 amountLD) {\\n return _amountSD * decimalConversionRate;\\n }\\n\\n /**\\n * @dev Internal function to convert an amount from local decimals into shared decimals.\\n * @param _amountLD The amount in local decimals.\\n * @return amountSD The amount in shared decimals.\\n *\\n * @dev Reverts if the _amountLD in shared decimals overflows uint64.\\n * @dev eg. uint(2**64 + 123) with a conversion rate of 1 wraps around 2**64 to uint(123).\\n */\\n function _toSD(uint256 _amountLD) internal view virtual returns (uint64 amountSD) {\\n uint256 _amountSD = _amountLD / decimalConversionRate;\\n if (_amountSD > type(uint64).max) revert AmountSDOverflowed(_amountSD);\\n return uint64(_amountSD);\\n }\\n\\n /**\\n * @dev Internal function to mock the amount mutation from a OFT debit() operation.\\n * @param _amountLD The amount to send in local decimals.\\n * @param _minAmountLD The minimum amount to send in local decimals.\\n * @dev _dstEid The destination endpoint ID.\\n * @return amountSentLD The amount sent, in local decimals.\\n * @return amountReceivedLD The amount to be received on the remote chain, in local decimals.\\n *\\n * @dev This is where things like fees would be calculated and deducted from the amount to be received on the remote.\\n */\\n function _debitView(\\n uint256 _amountLD,\\n uint256 _minAmountLD,\\n uint32 /*_dstEid*/\\n ) internal view virtual returns (uint256 amountSentLD, uint256 amountReceivedLD) {\\n // @dev Remove the dust so nothing is lost on the conversion between chains with different decimals for the token.\\n amountSentLD = _removeDust(_amountLD);\\n // @dev The amount to send is the same as amount received in the default implementation.\\n amountReceivedLD = amountSentLD;\\n\\n // @dev Check for slippage.\\n if (amountReceivedLD < _minAmountLD) {\\n revert SlippageExceeded(amountReceivedLD, _minAmountLD);\\n }\\n }\\n\\n /**\\n * @dev Internal function to perform a debit operation.\\n * @param _from The address to debit.\\n * @param _amountLD The amount to send in local decimals.\\n * @param _minAmountLD The minimum amount to send in local decimals.\\n * @param _dstEid The destination endpoint ID.\\n * @return amountSentLD The amount sent in local decimals.\\n * @return amountReceivedLD The amount received in local decimals on the remote.\\n *\\n * @dev Defined here but are intended to be overriden depending on the OFT implementation.\\n * @dev Depending on OFT implementation the _amountLD could differ from the amountReceivedLD.\\n */\\n function _debit(\\n address _from,\\n uint256 _amountLD,\\n uint256 _minAmountLD,\\n uint32 _dstEid\\n ) internal virtual returns (uint256 amountSentLD, uint256 amountReceivedLD);\\n\\n /**\\n * @dev Internal function to perform a credit operation.\\n * @param _to The address to credit.\\n * @param _amountLD The amount to credit in local decimals.\\n * @param _srcEid The source endpoint ID.\\n * @return amountReceivedLD The amount ACTUALLY received in local decimals.\\n *\\n * @dev Defined here but are intended to be overriden depending on the OFT implementation.\\n * @dev Depending on OFT implementation the _amountLD could differ from the amountReceivedLD.\\n */\\n function _credit(\\n address _to,\\n uint256 _amountLD,\\n uint32 _srcEid\\n ) internal virtual returns (uint256 amountReceivedLD);\\n}\\n\",\"keccak256\":\"0xdda89798c66928bba9e0fa44b3edf4710ff15cf46edadcf3e15c92d78fcc9ca8\",\"license\":\"MIT\"},\"@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { MessagingReceipt, MessagingFee } from \\\"@layerzerolabs/oapp-evm/contracts/oapp/OAppSender.sol\\\";\\n\\n/**\\n * @dev Struct representing token parameters for the OFT send() operation.\\n */\\nstruct SendParam {\\n uint32 dstEid; // Destination endpoint ID.\\n bytes32 to; // Recipient address.\\n uint256 amountLD; // Amount to send in local decimals.\\n uint256 minAmountLD; // Minimum amount to send in local decimals.\\n bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message.\\n bytes composeMsg; // The composed message for the send() operation.\\n bytes oftCmd; // The OFT command to be executed, unused in default OFT implementations.\\n}\\n\\n/**\\n * @dev Struct representing OFT limit information.\\n * @dev These amounts can change dynamically and are up the specific oft implementation.\\n */\\nstruct OFTLimit {\\n uint256 minAmountLD; // Minimum amount in local decimals that can be sent to the recipient.\\n uint256 maxAmountLD; // Maximum amount in local decimals that can be sent to the recipient.\\n}\\n\\n/**\\n * @dev Struct representing OFT receipt information.\\n */\\nstruct OFTReceipt {\\n uint256 amountSentLD; // Amount of tokens ACTUALLY debited from the sender in local decimals.\\n // @dev In non-default implementations, the amountReceivedLD COULD differ from this value.\\n uint256 amountReceivedLD; // Amount of tokens to be received on the remote side.\\n}\\n\\n/**\\n * @dev Struct representing OFT fee details.\\n * @dev Future proof mechanism to provide a standardized way to communicate fees to things like a UI.\\n */\\nstruct OFTFeeDetail {\\n int256 feeAmountLD; // Amount of the fee in local decimals.\\n string description; // Description of the fee.\\n}\\n\\n/**\\n * @title IOFT\\n * @dev Interface for the OftChain (OFT) token.\\n * @dev Does not inherit ERC20 to accommodate usage by OFTAdapter as well.\\n * @dev This specific interface ID is '0x02e49c2c'.\\n */\\ninterface IOFT {\\n // Custom error messages\\n error InvalidLocalDecimals();\\n error SlippageExceeded(uint256 amountLD, uint256 minAmountLD);\\n error AmountSDOverflowed(uint256 amountSD);\\n\\n // Events\\n event OFTSent(\\n bytes32 indexed guid, // GUID of the OFT message.\\n uint32 dstEid, // Destination Endpoint ID.\\n address indexed fromAddress, // Address of the sender on the src chain.\\n uint256 amountSentLD, // Amount of tokens sent in local decimals.\\n uint256 amountReceivedLD // Amount of tokens received in local decimals.\\n );\\n event OFTReceived(\\n bytes32 indexed guid, // GUID of the OFT message.\\n uint32 srcEid, // Source Endpoint ID.\\n address indexed toAddress, // Address of the recipient on the dst chain.\\n uint256 amountReceivedLD // Amount of tokens received in local decimals.\\n );\\n\\n /**\\n * @notice Retrieves interfaceID and the version of the OFT.\\n * @return interfaceId The interface ID.\\n * @return version The version.\\n *\\n * @dev interfaceId: This specific interface ID is '0x02e49c2c'.\\n * @dev version: Indicates a cross-chain compatible msg encoding with other OFTs.\\n * @dev If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented.\\n * ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)\\n */\\n function oftVersion() external view returns (bytes4 interfaceId, uint64 version);\\n\\n /**\\n * @notice Retrieves the address of the token associated with the OFT.\\n * @return token The address of the ERC20 token implementation.\\n */\\n function token() external view returns (address);\\n\\n /**\\n * @notice Indicates whether the OFT contract requires approval of the 'token()' to send.\\n * @return requiresApproval Needs approval of the underlying token implementation.\\n *\\n * @dev Allows things like wallet implementers to determine integration requirements,\\n * without understanding the underlying token implementation.\\n */\\n function approvalRequired() external view returns (bool);\\n\\n /**\\n * @notice Retrieves the shared decimals of the OFT.\\n * @return sharedDecimals The shared decimals of the OFT.\\n */\\n function sharedDecimals() external view returns (uint8);\\n\\n /**\\n * @notice Provides the fee breakdown and settings data for an OFT. Unused in the default implementation.\\n * @param _sendParam The parameters for the send operation.\\n * @return limit The OFT limit information.\\n * @return oftFeeDetails The details of OFT fees.\\n * @return receipt The OFT receipt information.\\n */\\n function quoteOFT(\\n SendParam calldata _sendParam\\n ) external view returns (OFTLimit memory, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory);\\n\\n /**\\n * @notice Provides a quote for the send() operation.\\n * @param _sendParam The parameters for the send() operation.\\n * @param _payInLzToken Flag indicating whether the caller is paying in the LZ token.\\n * @return fee The calculated LayerZero messaging fee from the send() operation.\\n *\\n * @dev MessagingFee: LayerZero msg fee\\n * - nativeFee: The native fee.\\n * - lzTokenFee: The lzToken fee.\\n */\\n function quoteSend(SendParam calldata _sendParam, bool _payInLzToken) external view returns (MessagingFee memory);\\n\\n /**\\n * @notice Executes the send() operation.\\n * @param _sendParam The parameters for the send operation.\\n * @param _fee The fee information supplied by the caller.\\n * - nativeFee: The native fee.\\n * - lzTokenFee: The lzToken fee.\\n * @param _refundAddress The address to receive any excess funds from fees etc. on the src.\\n * @return receipt The LayerZero messaging receipt from the send() operation.\\n * @return oftReceipt The OFT receipt information.\\n *\\n * @dev MessagingReceipt: LayerZero msg receipt\\n * - guid: The unique identifier for the sent message.\\n * - nonce: The nonce of the sent message.\\n * - fee: The LayerZero fee incurred for the message.\\n */\\n function send(\\n SendParam calldata _sendParam,\\n MessagingFee calldata _fee,\\n address _refundAddress\\n ) external payable returns (MessagingReceipt memory, OFTReceipt memory);\\n}\\n\",\"keccak256\":\"0xc60c7b4374b3d89f33b8de982f463c92374a8548800c816fe776f0ec76351fb0\",\"license\":\"MIT\"},\"@layerzerolabs/oft-evm/contracts/libs/OFTComposeMsgCodec.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nlibrary OFTComposeMsgCodec {\\n // Offset constants for decoding composed messages\\n uint8 private constant NONCE_OFFSET = 8;\\n uint8 private constant SRC_EID_OFFSET = 12;\\n uint8 private constant AMOUNT_LD_OFFSET = 44;\\n uint8 private constant COMPOSE_FROM_OFFSET = 76;\\n\\n /**\\n * @dev Encodes a OFT composed message.\\n * @param _nonce The nonce value.\\n * @param _srcEid The source endpoint ID.\\n * @param _amountLD The amount in local decimals.\\n * @param _composeMsg The composed message.\\n * @return _msg The encoded Composed message.\\n */\\n function encode(\\n uint64 _nonce,\\n uint32 _srcEid,\\n uint256 _amountLD,\\n bytes memory _composeMsg // 0x[composeFrom][composeMsg]\\n ) internal pure returns (bytes memory _msg) {\\n _msg = abi.encodePacked(_nonce, _srcEid, _amountLD, _composeMsg);\\n }\\n\\n /**\\n * @dev Retrieves the nonce for the composed message.\\n * @param _msg The message.\\n * @return The nonce value.\\n */\\n function nonce(bytes calldata _msg) internal pure returns (uint64) {\\n return uint64(bytes8(_msg[:NONCE_OFFSET]));\\n }\\n\\n /**\\n * @dev Retrieves the source endpoint ID for the composed message.\\n * @param _msg The message.\\n * @return The source endpoint ID.\\n */\\n function srcEid(bytes calldata _msg) internal pure returns (uint32) {\\n return uint32(bytes4(_msg[NONCE_OFFSET:SRC_EID_OFFSET]));\\n }\\n\\n /**\\n * @dev Retrieves the amount in local decimals from the composed message.\\n * @param _msg The message.\\n * @return The amount in local decimals.\\n */\\n function amountLD(bytes calldata _msg) internal pure returns (uint256) {\\n return uint256(bytes32(_msg[SRC_EID_OFFSET:AMOUNT_LD_OFFSET]));\\n }\\n\\n /**\\n * @dev Retrieves the composeFrom value from the composed message.\\n * @param _msg The message.\\n * @return The composeFrom value.\\n */\\n function composeFrom(bytes calldata _msg) internal pure returns (bytes32) {\\n return bytes32(_msg[AMOUNT_LD_OFFSET:COMPOSE_FROM_OFFSET]);\\n }\\n\\n /**\\n * @dev Retrieves the composed message.\\n * @param _msg The message.\\n * @return The composed message.\\n */\\n function composeMsg(bytes calldata _msg) internal pure returns (bytes memory) {\\n return _msg[COMPOSE_FROM_OFFSET:];\\n }\\n\\n /**\\n * @dev Converts an address to bytes32.\\n * @param _addr The address to convert.\\n * @return The bytes32 representation of the address.\\n */\\n function addressToBytes32(address _addr) internal pure returns (bytes32) {\\n return bytes32(uint256(uint160(_addr)));\\n }\\n\\n /**\\n * @dev Converts bytes32 to an address.\\n * @param _b The bytes32 value to convert.\\n * @return The address representation of bytes32.\\n */\\n function bytes32ToAddress(bytes32 _b) internal pure returns (address) {\\n return address(uint160(uint256(_b)));\\n }\\n}\\n\",\"keccak256\":\"0xaae73d6eb8b9561c43f1802f3c416c00ccd35f172b711f9781ccdf1b25a40db5\",\"license\":\"MIT\"},\"@layerzerolabs/oft-evm/contracts/libs/OFTMsgCodec.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nlibrary OFTMsgCodec {\\n // Offset constants for encoding and decoding OFT messages\\n uint8 private constant SEND_TO_OFFSET = 32;\\n uint8 private constant SEND_AMOUNT_SD_OFFSET = 40;\\n\\n /**\\n * @dev Encodes an OFT LayerZero message.\\n * @param _sendTo The recipient address.\\n * @param _amountShared The amount in shared decimals.\\n * @param _composeMsg The composed message.\\n * @return _msg The encoded message.\\n * @return hasCompose A boolean indicating whether the message has a composed payload.\\n */\\n function encode(\\n bytes32 _sendTo,\\n uint64 _amountShared,\\n bytes memory _composeMsg\\n ) internal view returns (bytes memory _msg, bool hasCompose) {\\n hasCompose = _composeMsg.length > 0;\\n // @dev Remote chains will want to know the composed function caller ie. msg.sender on the src.\\n _msg = hasCompose\\n ? abi.encodePacked(_sendTo, _amountShared, addressToBytes32(msg.sender), _composeMsg)\\n : abi.encodePacked(_sendTo, _amountShared);\\n }\\n\\n /**\\n * @dev Checks if the OFT message is composed.\\n * @param _msg The OFT message.\\n * @return A boolean indicating whether the message is composed.\\n */\\n function isComposed(bytes calldata _msg) internal pure returns (bool) {\\n return _msg.length > SEND_AMOUNT_SD_OFFSET;\\n }\\n\\n /**\\n * @dev Retrieves the recipient address from the OFT message.\\n * @param _msg The OFT message.\\n * @return The recipient address.\\n */\\n function sendTo(bytes calldata _msg) internal pure returns (bytes32) {\\n return bytes32(_msg[:SEND_TO_OFFSET]);\\n }\\n\\n /**\\n * @dev Retrieves the amount in shared decimals from the OFT message.\\n * @param _msg The OFT message.\\n * @return The amount in shared decimals.\\n */\\n function amountSD(bytes calldata _msg) internal pure returns (uint64) {\\n return uint64(bytes8(_msg[SEND_TO_OFFSET:SEND_AMOUNT_SD_OFFSET]));\\n }\\n\\n /**\\n * @dev Retrieves the composed message from the OFT message.\\n * @param _msg The OFT message.\\n * @return The composed message.\\n */\\n function composeMsg(bytes calldata _msg) internal pure returns (bytes memory) {\\n return _msg[SEND_AMOUNT_SD_OFFSET:];\\n }\\n\\n /**\\n * @dev Converts an address to bytes32.\\n * @param _addr The address to convert.\\n * @return The bytes32 representation of the address.\\n */\\n function addressToBytes32(address _addr) internal pure returns (bytes32) {\\n return bytes32(uint256(uint160(_addr)));\\n }\\n\\n /**\\n * @dev Converts bytes32 to an address.\\n * @param _b The bytes32 value to convert.\\n * @return The address representation of bytes32.\\n */\\n function bytes32ToAddress(bytes32 _b) internal pure returns (address) {\\n return address(uint160(uint256(_b)));\\n }\\n}\\n\",\"keccak256\":\"0x5358948017669c03e157f871d8c38e988f9004dbd0801ad3119d2487f0d40b0b\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/Ownable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\\n\\npragma solidity ^0.8.20;\\n\\nimport {Context} from \\\"../utils/Context.sol\\\";\\n\\n/**\\n * @dev Contract module which provides a basic access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * The initial owner is set to the address provided by the deployer. This can\\n * later be changed with {transferOwnership}.\\n *\\n * This module is used through inheritance. It will make available the modifier\\n * `onlyOwner`, which can be applied to your functions to restrict their use to\\n * the owner.\\n */\\nabstract contract Ownable is Context {\\n address private _owner;\\n\\n /**\\n * @dev The caller account is not authorized to perform an operation.\\n */\\n error OwnableUnauthorizedAccount(address account);\\n\\n /**\\n * @dev The owner is not a valid owner account. (eg. `address(0)`)\\n */\\n error OwnableInvalidOwner(address owner);\\n\\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\\n\\n /**\\n * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\\n */\\n constructor(address initialOwner) {\\n if (initialOwner == address(0)) {\\n revert OwnableInvalidOwner(address(0));\\n }\\n _transferOwnership(initialOwner);\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the owner.\\n */\\n modifier onlyOwner() {\\n _checkOwner();\\n _;\\n }\\n\\n /**\\n * @dev Returns the address of the current owner.\\n */\\n function owner() public view virtual returns (address) {\\n return _owner;\\n }\\n\\n /**\\n * @dev Throws if the sender is not the owner.\\n */\\n function _checkOwner() internal view virtual {\\n if (owner() != _msgSender()) {\\n revert OwnableUnauthorizedAccount(_msgSender());\\n }\\n }\\n\\n /**\\n * @dev Leaves the contract without owner. It will not be possible to call\\n * `onlyOwner` functions. Can only be called by the current owner.\\n *\\n * NOTE: Renouncing ownership will leave the contract without an owner,\\n * thereby disabling any functionality that is only available to the owner.\\n */\\n function renounceOwnership() public virtual onlyOwner {\\n _transferOwnership(address(0));\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual onlyOwner {\\n if (newOwner == address(0)) {\\n revert OwnableInvalidOwner(address(0));\\n }\\n _transferOwnership(newOwner);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Internal function without access restriction.\\n */\\n function _transferOwnership(address newOwner) internal virtual {\\n address oldOwner = _owner;\\n _owner = newOwner;\\n emit OwnershipTransferred(oldOwner, newOwner);\\n }\\n}\\n\",\"keccak256\":\"0xff6d0bb2e285473e5311d9d3caacb525ae3538a80758c10649a4d61029b017bb\",\"license\":\"MIT\"},\"@openzeppelin/contracts/interfaces/IERC1363.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1363.sol)\\n\\npragma solidity >=0.6.2;\\n\\nimport {IERC20} from \\\"./IERC20.sol\\\";\\nimport {IERC165} from \\\"./IERC165.sol\\\";\\n\\n/**\\n * @title IERC1363\\n * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].\\n *\\n * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract\\n * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.\\n */\\ninterface IERC1363 is IERC20, IERC165 {\\n /*\\n * Note: the ERC-165 identifier for this interface is 0xb0202a11.\\n * 0xb0202a11 ===\\n * bytes4(keccak256('transferAndCall(address,uint256)')) ^\\n * bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^\\n * bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^\\n * bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^\\n * bytes4(keccak256('approveAndCall(address,uint256)')) ^\\n * bytes4(keccak256('approveAndCall(address,uint256,bytes)'))\\n */\\n\\n /**\\n * @dev Moves a `value` amount of tokens from the caller's account to `to`\\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\\n * @param to The address which you want to transfer to.\\n * @param value The amount of tokens to be transferred.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function transferAndCall(address to, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from the caller's account to `to`\\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\\n * @param to The address which you want to transfer to.\\n * @param value The amount of tokens to be transferred.\\n * @param data Additional data with no specified format, sent in call to `to`.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism\\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\\n * @param from The address which you want to send tokens from.\\n * @param to The address which you want to transfer to.\\n * @param value The amount of tokens to be transferred.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function transferFromAndCall(address from, address to, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism\\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\\n * @param from The address which you want to send tokens from.\\n * @param to The address which you want to transfer to.\\n * @param value The amount of tokens to be transferred.\\n * @param data Additional data with no specified format, sent in call to `to`.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);\\n\\n /**\\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\\n * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.\\n * @param spender The address which will spend the funds.\\n * @param value The amount of tokens to be spent.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function approveAndCall(address spender, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\\n * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.\\n * @param spender The address which will spend the funds.\\n * @param value The amount of tokens to be spent.\\n * @param data Additional data with no specified format, sent in call to `spender`.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);\\n}\\n\",\"keccak256\":\"0xd5ea07362ab630a6a3dee4285a74cf2377044ca2e4be472755ad64d7c5d4b69d\",\"license\":\"MIT\"},\"@openzeppelin/contracts/interfaces/IERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC165.sol)\\n\\npragma solidity >=0.4.16;\\n\\nimport {IERC165} from \\\"../utils/introspection/IERC165.sol\\\";\\n\",\"keccak256\":\"0x0afcb7e740d1537b252cb2676f600465ce6938398569f09ba1b9ca240dde2dfc\",\"license\":\"MIT\"},\"@openzeppelin/contracts/interfaces/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC20.sol)\\n\\npragma solidity >=0.4.16;\\n\\nimport {IERC20} from \\\"../token/ERC20/IERC20.sol\\\";\\n\",\"keccak256\":\"0x1a6221315ce0307746c2c4827c125d821ee796c74a676787762f4778671d4f44\",\"license\":\"MIT\"},\"@openzeppelin/contracts/interfaces/draft-IERC6093.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.5.0) (interfaces/draft-IERC6093.sol)\\n\\npragma solidity >=0.8.4;\\n\\n/**\\n * @dev Standard ERC-20 Errors\\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-20 tokens.\\n */\\ninterface IERC20Errors {\\n /**\\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n * @param balance Current balance for the interacting account.\\n * @param needed Minimum amount required to perform a transfer.\\n */\\n error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);\\n\\n /**\\n * @dev Indicates a failure with the token `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n */\\n error ERC20InvalidSender(address sender);\\n\\n /**\\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\\n * @param receiver Address to which tokens are being transferred.\\n */\\n error ERC20InvalidReceiver(address receiver);\\n\\n /**\\n * @dev Indicates a failure with the `spender`\\u2019s `allowance`. Used in transfers.\\n * @param spender Address that may be allowed to operate on tokens without being their owner.\\n * @param allowance Amount of tokens a `spender` is allowed to operate with.\\n * @param needed Minimum amount required to perform a transfer.\\n */\\n error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);\\n\\n /**\\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\\n * @param approver Address initiating an approval operation.\\n */\\n error ERC20InvalidApprover(address approver);\\n\\n /**\\n * @dev Indicates a failure with the `spender` to be approved. Used in approvals.\\n * @param spender Address that may be allowed to operate on tokens without being their owner.\\n */\\n error ERC20InvalidSpender(address spender);\\n}\\n\\n/**\\n * @dev Standard ERC-721 Errors\\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-721 tokens.\\n */\\ninterface IERC721Errors {\\n /**\\n * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-721.\\n * Used in balance queries.\\n * @param owner Address of the current owner of a token.\\n */\\n error ERC721InvalidOwner(address owner);\\n\\n /**\\n * @dev Indicates a `tokenId` whose `owner` is the zero address.\\n * @param tokenId Identifier number of a token.\\n */\\n error ERC721NonexistentToken(uint256 tokenId);\\n\\n /**\\n * @dev Indicates an error related to the ownership over a particular token. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n * @param tokenId Identifier number of a token.\\n * @param owner Address of the current owner of a token.\\n */\\n error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);\\n\\n /**\\n * @dev Indicates a failure with the token `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n */\\n error ERC721InvalidSender(address sender);\\n\\n /**\\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\\n * @param receiver Address to which tokens are being transferred.\\n */\\n error ERC721InvalidReceiver(address receiver);\\n\\n /**\\n * @dev Indicates a failure with the `operator`\\u2019s approval. Used in transfers.\\n * @param operator Address that may be allowed to operate on tokens without being their owner.\\n * @param tokenId Identifier number of a token.\\n */\\n error ERC721InsufficientApproval(address operator, uint256 tokenId);\\n\\n /**\\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\\n * @param approver Address initiating an approval operation.\\n */\\n error ERC721InvalidApprover(address approver);\\n\\n /**\\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\\n * @param operator Address that may be allowed to operate on tokens without being their owner.\\n */\\n error ERC721InvalidOperator(address operator);\\n}\\n\\n/**\\n * @dev Standard ERC-1155 Errors\\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-1155 tokens.\\n */\\ninterface IERC1155Errors {\\n /**\\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n * @param balance Current balance for the interacting account.\\n * @param needed Minimum amount required to perform a transfer.\\n * @param tokenId Identifier number of a token.\\n */\\n error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);\\n\\n /**\\n * @dev Indicates a failure with the token `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n */\\n error ERC1155InvalidSender(address sender);\\n\\n /**\\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\\n * @param receiver Address to which tokens are being transferred.\\n */\\n error ERC1155InvalidReceiver(address receiver);\\n\\n /**\\n * @dev Indicates a failure with the `operator`\\u2019s approval. Used in transfers.\\n * @param operator Address that may be allowed to operate on tokens without being their owner.\\n * @param owner Address of the current owner of a token.\\n */\\n error ERC1155MissingApprovalForAll(address operator, address owner);\\n\\n /**\\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\\n * @param approver Address initiating an approval operation.\\n */\\n error ERC1155InvalidApprover(address approver);\\n\\n /**\\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\\n * @param operator Address that may be allowed to operate on tokens without being their owner.\\n */\\n error ERC1155InvalidOperator(address operator);\\n\\n /**\\n * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.\\n * Used in batch transfers.\\n * @param idsLength Length of the array of token identifiers\\n * @param valuesLength Length of the array of token amounts\\n */\\n error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);\\n}\\n\",\"keccak256\":\"0x1b88b3fb3d85ba5496d7d5f396f83ee1fddcdd6762059ff65992655b67920998\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/ERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.5.0) (token/ERC20/ERC20.sol)\\n\\npragma solidity ^0.8.20;\\n\\nimport {IERC20} from \\\"./IERC20.sol\\\";\\nimport {IERC20Metadata} from \\\"./extensions/IERC20Metadata.sol\\\";\\nimport {Context} from \\\"../../utils/Context.sol\\\";\\nimport {IERC20Errors} from \\\"../../interfaces/draft-IERC6093.sol\\\";\\n\\n/**\\n * @dev Implementation of the {IERC20} interface.\\n *\\n * This implementation is agnostic to the way tokens are created. This means\\n * that a supply mechanism has to be added in a derived contract using {_mint}.\\n *\\n * TIP: For a detailed writeup see our guide\\n * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How\\n * to implement supply mechanisms].\\n *\\n * The default value of {decimals} is 18. To change this, you should override\\n * this function so it returns a different value.\\n *\\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\\n * instead returning `false` on failure. This behavior is nonetheless\\n * conventional and does not conflict with the expectations of ERC-20\\n * applications.\\n */\\nabstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {\\n mapping(address account => uint256) private _balances;\\n\\n mapping(address account => mapping(address spender => uint256)) private _allowances;\\n\\n uint256 private _totalSupply;\\n\\n string private _name;\\n string private _symbol;\\n\\n /**\\n * @dev Sets the values for {name} and {symbol}.\\n *\\n * Both values are immutable: they can only be set once during construction.\\n */\\n constructor(string memory name_, string memory symbol_) {\\n _name = name_;\\n _symbol = symbol_;\\n }\\n\\n /**\\n * @dev Returns the name of the token.\\n */\\n function name() public view virtual returns (string memory) {\\n return _name;\\n }\\n\\n /**\\n * @dev Returns the symbol of the token, usually a shorter version of the\\n * name.\\n */\\n function symbol() public view virtual returns (string memory) {\\n return _symbol;\\n }\\n\\n /**\\n * @dev Returns the number of decimals used to get its user representation.\\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\\n *\\n * Tokens usually opt for a value of 18, imitating the relationship between\\n * Ether and Wei. This is the default value returned by this function, unless\\n * it's overridden.\\n *\\n * NOTE: This information is only used for _display_ purposes: it in\\n * no way affects any of the arithmetic of the contract, including\\n * {IERC20-balanceOf} and {IERC20-transfer}.\\n */\\n function decimals() public view virtual returns (uint8) {\\n return 18;\\n }\\n\\n /// @inheritdoc IERC20\\n function totalSupply() public view virtual returns (uint256) {\\n return _totalSupply;\\n }\\n\\n /// @inheritdoc IERC20\\n function balanceOf(address account) public view virtual returns (uint256) {\\n return _balances[account];\\n }\\n\\n /**\\n * @dev See {IERC20-transfer}.\\n *\\n * Requirements:\\n *\\n * - `to` cannot be the zero address.\\n * - the caller must have a balance of at least `value`.\\n */\\n function transfer(address to, uint256 value) public virtual returns (bool) {\\n address owner = _msgSender();\\n _transfer(owner, to, value);\\n return true;\\n }\\n\\n /// @inheritdoc IERC20\\n function allowance(address owner, address spender) public view virtual returns (uint256) {\\n return _allowances[owner][spender];\\n }\\n\\n /**\\n * @dev See {IERC20-approve}.\\n *\\n * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on\\n * `transferFrom`. This is semantically equivalent to an infinite approval.\\n *\\n * Requirements:\\n *\\n * - `spender` cannot be the zero address.\\n */\\n function approve(address spender, uint256 value) public virtual returns (bool) {\\n address owner = _msgSender();\\n _approve(owner, spender, value);\\n return true;\\n }\\n\\n /**\\n * @dev See {IERC20-transferFrom}.\\n *\\n * Skips emitting an {Approval} event indicating an allowance update. This is not\\n * required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].\\n *\\n * NOTE: Does not update the allowance if the current allowance\\n * is the maximum `uint256`.\\n *\\n * Requirements:\\n *\\n * - `from` and `to` cannot be the zero address.\\n * - `from` must have a balance of at least `value`.\\n * - the caller must have allowance for ``from``'s tokens of at least\\n * `value`.\\n */\\n function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {\\n address spender = _msgSender();\\n _spendAllowance(from, spender, value);\\n _transfer(from, to, value);\\n return true;\\n }\\n\\n /**\\n * @dev Moves a `value` amount of tokens from `from` to `to`.\\n *\\n * This internal function is equivalent to {transfer}, and can be used to\\n * e.g. implement automatic token fees, slashing mechanisms, etc.\\n *\\n * Emits a {Transfer} event.\\n *\\n * NOTE: This function is not virtual, {_update} should be overridden instead.\\n */\\n function _transfer(address from, address to, uint256 value) internal {\\n if (from == address(0)) {\\n revert ERC20InvalidSender(address(0));\\n }\\n if (to == address(0)) {\\n revert ERC20InvalidReceiver(address(0));\\n }\\n _update(from, to, value);\\n }\\n\\n /**\\n * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`\\n * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding\\n * this function.\\n *\\n * Emits a {Transfer} event.\\n */\\n function _update(address from, address to, uint256 value) internal virtual {\\n if (from == address(0)) {\\n // Overflow check required: The rest of the code assumes that totalSupply never overflows\\n _totalSupply += value;\\n } else {\\n uint256 fromBalance = _balances[from];\\n if (fromBalance < value) {\\n revert ERC20InsufficientBalance(from, fromBalance, value);\\n }\\n unchecked {\\n // Overflow not possible: value <= fromBalance <= totalSupply.\\n _balances[from] = fromBalance - value;\\n }\\n }\\n\\n if (to == address(0)) {\\n unchecked {\\n // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.\\n _totalSupply -= value;\\n }\\n } else {\\n unchecked {\\n // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.\\n _balances[to] += value;\\n }\\n }\\n\\n emit Transfer(from, to, value);\\n }\\n\\n /**\\n * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).\\n * Relies on the `_update` mechanism\\n *\\n * Emits a {Transfer} event with `from` set to the zero address.\\n *\\n * NOTE: This function is not virtual, {_update} should be overridden instead.\\n */\\n function _mint(address account, uint256 value) internal {\\n if (account == address(0)) {\\n revert ERC20InvalidReceiver(address(0));\\n }\\n _update(address(0), account, value);\\n }\\n\\n /**\\n * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.\\n * Relies on the `_update` mechanism.\\n *\\n * Emits a {Transfer} event with `to` set to the zero address.\\n *\\n * NOTE: This function is not virtual, {_update} should be overridden instead\\n */\\n function _burn(address account, uint256 value) internal {\\n if (account == address(0)) {\\n revert ERC20InvalidSender(address(0));\\n }\\n _update(account, address(0), value);\\n }\\n\\n /**\\n * @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.\\n *\\n * This internal function is equivalent to `approve`, and can be used to\\n * e.g. set automatic allowances for certain subsystems, etc.\\n *\\n * Emits an {Approval} event.\\n *\\n * Requirements:\\n *\\n * - `owner` cannot be the zero address.\\n * - `spender` cannot be the zero address.\\n *\\n * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.\\n */\\n function _approve(address owner, address spender, uint256 value) internal {\\n _approve(owner, spender, value, true);\\n }\\n\\n /**\\n * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.\\n *\\n * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by\\n * `_spendAllowance` during the `transferFrom` operation sets the flag to false. This saves gas by not emitting any\\n * `Approval` event during `transferFrom` operations.\\n *\\n * Anyone who wishes to continue emitting `Approval` events on the `transferFrom` operation can force the flag to\\n * true using the following override:\\n *\\n * ```solidity\\n * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {\\n * super._approve(owner, spender, value, true);\\n * }\\n * ```\\n *\\n * Requirements are the same as {_approve}.\\n */\\n function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {\\n if (owner == address(0)) {\\n revert ERC20InvalidApprover(address(0));\\n }\\n if (spender == address(0)) {\\n revert ERC20InvalidSpender(address(0));\\n }\\n _allowances[owner][spender] = value;\\n if (emitEvent) {\\n emit Approval(owner, spender, value);\\n }\\n }\\n\\n /**\\n * @dev Updates `owner`'s allowance for `spender` based on spent `value`.\\n *\\n * Does not update the allowance value in case of infinite allowance.\\n * Revert if not enough allowance is available.\\n *\\n * Does not emit an {Approval} event.\\n */\\n function _spendAllowance(address owner, address spender, uint256 value) internal virtual {\\n uint256 currentAllowance = allowance(owner, spender);\\n if (currentAllowance < type(uint256).max) {\\n if (currentAllowance < value) {\\n revert ERC20InsufficientAllowance(spender, currentAllowance, value);\\n }\\n unchecked {\\n _approve(owner, spender, currentAllowance - value, false);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x669464167428061ee0f8618b73b3ee90aff8405683e7ddde8cd77dadaa1afe29\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)\\n\\npragma solidity >=0.4.16;\\n\\n/**\\n * @dev Interface of the ERC-20 standard as defined in the ERC.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n\\n /**\\n * @dev Returns the value of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the value of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from the caller's account to `to`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address to, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\\n * caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from `from` to `to` using the\\n * allowance mechanism. `value` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(address from, address to, uint256 value) external returns (bool);\\n}\\n\",\"keccak256\":\"0x74ed01eb66b923d0d0cfe3be84604ac04b76482a55f9dd655e1ef4d367f95bc2\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/extensions/IERC20Metadata.sol)\\n\\npragma solidity >=0.6.2;\\n\\nimport {IERC20} from \\\"../IERC20.sol\\\";\\n\\n/**\\n * @dev Interface for the optional metadata functions from the ERC-20 standard.\\n */\\ninterface IERC20Metadata is IERC20 {\\n /**\\n * @dev Returns the name of the token.\\n */\\n function name() external view returns (string memory);\\n\\n /**\\n * @dev Returns the symbol of the token.\\n */\\n function symbol() external view returns (string memory);\\n\\n /**\\n * @dev Returns the decimals places of the token.\\n */\\n function decimals() external view returns (uint8);\\n}\\n\",\"keccak256\":\"0xd6fa4088198f04eef10c5bce8a2f4d60554b7ec4b987f684393c01bf79b94d9f\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.5.0) (token/ERC20/utils/SafeERC20.sol)\\n\\npragma solidity ^0.8.20;\\n\\nimport {IERC20} from \\\"../IERC20.sol\\\";\\nimport {IERC1363} from \\\"../../../interfaces/IERC1363.sol\\\";\\n\\n/**\\n * @title SafeERC20\\n * @dev Wrappers around ERC-20 operations that throw on failure (when the token\\n * contract returns false). Tokens that return no value (and instead revert or\\n * throw on failure) are also supported, non-reverting calls are assumed to be\\n * successful.\\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\\n */\\nlibrary SafeERC20 {\\n /**\\n * @dev An operation with an ERC-20 token failed.\\n */\\n error SafeERC20FailedOperation(address token);\\n\\n /**\\n * @dev Indicates a failed `decreaseAllowance` request.\\n */\\n error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);\\n\\n /**\\n * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful.\\n */\\n function safeTransfer(IERC20 token, address to, uint256 value) internal {\\n if (!_safeTransfer(token, to, value, true)) {\\n revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the\\n * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.\\n */\\n function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {\\n if (!_safeTransferFrom(token, from, to, value, true)) {\\n revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.\\n */\\n function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {\\n return _safeTransfer(token, to, value, false);\\n }\\n\\n /**\\n * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.\\n */\\n function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {\\n return _safeTransferFrom(token, from, to, value, false);\\n }\\n\\n /**\\n * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful.\\n *\\n * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the \\\"client\\\"\\n * smart contract uses ERC-7674 to set temporary allowances, then the \\\"client\\\" smart contract should avoid using\\n * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract\\n * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.\\n */\\n function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {\\n uint256 oldAllowance = token.allowance(address(this), spender);\\n forceApprove(token, spender, oldAllowance + value);\\n }\\n\\n /**\\n * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no\\n * value, non-reverting calls are assumed to be successful.\\n *\\n * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the \\\"client\\\"\\n * smart contract uses ERC-7674 to set temporary allowances, then the \\\"client\\\" smart contract should avoid using\\n * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract\\n * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.\\n */\\n function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {\\n unchecked {\\n uint256 currentAllowance = token.allowance(address(this), spender);\\n if (currentAllowance < requestedDecrease) {\\n revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);\\n }\\n forceApprove(token, spender, currentAllowance - requestedDecrease);\\n }\\n }\\n\\n /**\\n * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval\\n * to be set to zero before setting it to a non-zero value, such as USDT.\\n *\\n * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function\\n * only sets the \\\"standard\\\" allowance. Any temporary allowance will remain active, in addition to the value being\\n * set here.\\n */\\n function forceApprove(IERC20 token, address spender, uint256 value) internal {\\n if (!_safeApprove(token, spender, value, false)) {\\n if (!_safeApprove(token, spender, 0, true)) revert SafeERC20FailedOperation(address(token));\\n if (!_safeApprove(token, spender, value, true)) revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no\\n * code. This can be used to implement an {ERC721}-like safe transfer that relies on {ERC1363} checks when\\n * targeting contracts.\\n *\\n * Reverts if the returned value is other than `true`.\\n */\\n function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {\\n if (to.code.length == 0) {\\n safeTransfer(token, to, value);\\n } else if (!token.transferAndCall(to, value, data)) {\\n revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target\\n * has no code. This can be used to implement an {ERC721}-like safe transfer that relies on {ERC1363} checks when\\n * targeting contracts.\\n *\\n * Reverts if the returned value is other than `true`.\\n */\\n function transferFromAndCallRelaxed(\\n IERC1363 token,\\n address from,\\n address to,\\n uint256 value,\\n bytes memory data\\n ) internal {\\n if (to.code.length == 0) {\\n safeTransferFrom(token, from, to, value);\\n } else if (!token.transferFromAndCall(from, to, value, data)) {\\n revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no\\n * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when\\n * targeting contracts.\\n *\\n * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.\\n * Oppositely, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}\\n * once without retrying, and relies on the returned value to be true.\\n *\\n * Reverts if the returned value is other than `true`.\\n */\\n function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {\\n if (to.code.length == 0) {\\n forceApprove(token, to, value);\\n } else if (!token.approveAndCall(to, value, data)) {\\n revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Imitates a Solidity `token.transfer(to, value)` call, relaxing the requirement on the return value: the\\n * return value is optional (but if data is returned, it must not be false).\\n *\\n * @param token The token targeted by the call.\\n * @param to The recipient of the tokens\\n * @param value The amount of token to transfer\\n * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.\\n */\\n function _safeTransfer(IERC20 token, address to, uint256 value, bool bubble) private returns (bool success) {\\n bytes4 selector = IERC20.transfer.selector;\\n\\n assembly (\\\"memory-safe\\\") {\\n let fmp := mload(0x40)\\n mstore(0x00, selector)\\n mstore(0x04, and(to, shr(96, not(0))))\\n mstore(0x24, value)\\n success := call(gas(), token, 0, 0x00, 0x44, 0x00, 0x20)\\n // if call success and return is true, all is good.\\n // otherwise (not success or return is not true), we need to perform further checks\\n if iszero(and(success, eq(mload(0x00), 1))) {\\n // if the call was a failure and bubble is enabled, bubble the error\\n if and(iszero(success), bubble) {\\n returndatacopy(fmp, 0x00, returndatasize())\\n revert(fmp, returndatasize())\\n }\\n // if the return value is not true, then the call is only successful if:\\n // - the token address has code\\n // - the returndata is empty\\n success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))\\n }\\n mstore(0x40, fmp)\\n }\\n }\\n\\n /**\\n * @dev Imitates a Solidity `token.transferFrom(from, to, value)` call, relaxing the requirement on the return\\n * value: the return value is optional (but if data is returned, it must not be false).\\n *\\n * @param token The token targeted by the call.\\n * @param from The sender of the tokens\\n * @param to The recipient of the tokens\\n * @param value The amount of token to transfer\\n * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.\\n */\\n function _safeTransferFrom(\\n IERC20 token,\\n address from,\\n address to,\\n uint256 value,\\n bool bubble\\n ) private returns (bool success) {\\n bytes4 selector = IERC20.transferFrom.selector;\\n\\n assembly (\\\"memory-safe\\\") {\\n let fmp := mload(0x40)\\n mstore(0x00, selector)\\n mstore(0x04, and(from, shr(96, not(0))))\\n mstore(0x24, and(to, shr(96, not(0))))\\n mstore(0x44, value)\\n success := call(gas(), token, 0, 0x00, 0x64, 0x00, 0x20)\\n // if call success and return is true, all is good.\\n // otherwise (not success or return is not true), we need to perform further checks\\n if iszero(and(success, eq(mload(0x00), 1))) {\\n // if the call was a failure and bubble is enabled, bubble the error\\n if and(iszero(success), bubble) {\\n returndatacopy(fmp, 0x00, returndatasize())\\n revert(fmp, returndatasize())\\n }\\n // if the return value is not true, then the call is only successful if:\\n // - the token address has code\\n // - the returndata is empty\\n success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))\\n }\\n mstore(0x40, fmp)\\n mstore(0x60, 0)\\n }\\n }\\n\\n /**\\n * @dev Imitates a Solidity `token.approve(spender, value)` call, relaxing the requirement on the return value:\\n * the return value is optional (but if data is returned, it must not be false).\\n *\\n * @param token The token targeted by the call.\\n * @param spender The spender of the tokens\\n * @param value The amount of token to transfer\\n * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.\\n */\\n function _safeApprove(IERC20 token, address spender, uint256 value, bool bubble) private returns (bool success) {\\n bytes4 selector = IERC20.approve.selector;\\n\\n assembly (\\\"memory-safe\\\") {\\n let fmp := mload(0x40)\\n mstore(0x00, selector)\\n mstore(0x04, and(spender, shr(96, not(0))))\\n mstore(0x24, value)\\n success := call(gas(), token, 0, 0x00, 0x44, 0x00, 0x20)\\n // if call success and return is true, all is good.\\n // otherwise (not success or return is not true), we need to perform further checks\\n if iszero(and(success, eq(mload(0x00), 1))) {\\n // if the call was a failure and bubble is enabled, bubble the error\\n if and(iszero(success), bubble) {\\n returndatacopy(fmp, 0x00, returndatasize())\\n revert(fmp, returndatasize())\\n }\\n // if the return value is not true, then the call is only successful if:\\n // - the token address has code\\n // - the returndata is empty\\n success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))\\n }\\n mstore(0x40, fmp)\\n }\\n }\\n}\\n\",\"keccak256\":\"0x304d732678032a9781ae85c8f204c8fba3d3a5e31c02616964e75cfdc5049098\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Context.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\\n\\npragma solidity ^0.8.20;\\n\\n/**\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract Context {\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n\\n function _contextSuffixLength() internal view virtual returns (uint256) {\\n return 0;\\n }\\n}\\n\",\"keccak256\":\"0x493033a8d1b176a037b2cc6a04dad01a5c157722049bbecf632ca876224dd4b2\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/introspection/IERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)\\n\\npragma solidity >=0.4.16;\\n\\n/**\\n * @dev Interface of the ERC-165 standard, as defined in the\\n * https://eips.ethereum.org/EIPS/eip-165[ERC].\\n *\\n * Implementers can declare support of contract interfaces, which can then be\\n * queried by others ({ERC165Checker}).\\n *\\n * For an implementation, see {ERC165}.\\n */\\ninterface IERC165 {\\n /**\\n * @dev Returns true if this contract implements the interface defined by\\n * `interfaceId`. See the corresponding\\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]\\n * to learn more about how these ids are created.\\n *\\n * This function call must use less than 30 000 gas.\\n */\\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x8891738ffe910f0cf2da09566928589bf5d63f4524dd734fd9cedbac3274dd5c\",\"license\":\"MIT\"},\"contracts/MyOFT.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\r\\npragma solidity ^0.8.22;\\r\\n\\r\\nimport { Ownable } from \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\r\\nimport { OFT } from \\\"@layerzerolabs/oft-evm/contracts/OFT.sol\\\";\\r\\n\\r\\ncontract MyOFT is OFT {\\r\\n constructor(\\r\\n string memory _name,\\r\\n string memory _symbol,\\r\\n address _lzEndpoint,\\r\\n address _delegate\\r\\n ) OFT(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) {}\\r\\n\\r\\n /// @notice Open mint, TESTNET ONLY \\u2014 same as ToyOFT, so the demo tasks work against either.\\r\\n /// @dev Deliberately unguarded: the demo mints to itself before each send, and a testnet OFT\\r\\n /// with no way to obtain tokens cannot be used to exercise the DVN at all. Do NOT ship\\r\\n /// this to a network where the token has value.\\r\\n /// `virtual` because MyOFTMock declares the same function for the hardhat tests.\\r\\n function mint(address _to, uint256 _amount) public virtual {\\r\\n _mint(_to, _amount);\\r\\n }\\r\\n}\\r\\n\",\"keccak256\":\"0x742ac02bacb2a1fa397bf560593b446ea96b15f7031123129b0fa6bcbd4c0e80\",\"license\":\"UNLICENSED\"}},\"version\":1}", + "bytecode": "0x60c06040523480156200001157600080fd5b5060405162003790380380620037908339810160408190526200003491620002d2565b83838383838360128484818181818d6001600160a01b0381166200007257604051631e4fbdf760e01b81526000600482015260240160405180910390fd5b6200007d8162000198565b506001600160a01b038083166080528116620000ac57604051632d618d8160e21b815260040160405180910390fd5b60805160405163ca5eb5e160e01b81526001600160a01b0383811660048301529091169063ca5eb5e190602401600060405180830381600087803b158015620000f457600080fd5b505af115801562000109573d6000803e3d6000fd5b505050505050505062000121620001e860201b60201c565b60ff168360ff16101562000148576040516301e9714b60e41b815260040160405180910390fd5b6200015560068462000377565b6200016290600a62000496565b60a052506008915062000178905083826200053f565b5060096200018782826200053f565b50505050505050505050506200060b565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b600690565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126200021557600080fd5b81516001600160401b0380821115620002325762000232620001ed565b604051601f8301601f19908116603f011681019082821181831017156200025d576200025d620001ed565b81604052838152602092508660208588010111156200027b57600080fd5b600091505b838210156200029f578582018301518183018401529082019062000280565b6000602085830101528094505050505092915050565b80516001600160a01b0381168114620002cd57600080fd5b919050565b60008060008060808587031215620002e957600080fd5b84516001600160401b03808211156200030157600080fd5b6200030f8883890162000203565b955060208701519150808211156200032657600080fd5b50620003358782880162000203565b9350506200034660408601620002b5565b91506200035660608601620002b5565b905092959194509250565b634e487b7160e01b600052601160045260246000fd5b60ff828116828216039081111562000393576200039362000361565b92915050565b600181815b80851115620003da578160001904821115620003be57620003be62000361565b80851615620003cc57918102915b93841c93908002906200039e565b509250929050565b600082620003f35750600162000393565b81620004025750600062000393565b81600181146200041b5760028114620004265762000446565b600191505062000393565b60ff8411156200043a576200043a62000361565b50506001821b62000393565b5060208310610133831016604e8410600b84101617156200046b575081810a62000393565b62000477838362000399565b80600019048211156200048e576200048e62000361565b029392505050565b6000620004a760ff841683620003e2565b9392505050565b600181811c90821680620004c357607f821691505b602082108103620004e457634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200053a576000816000526020600020601f850160051c81016020861015620005155750805b601f850160051c820191505b81811015620005365782815560010162000521565b5050505b505050565b81516001600160401b038111156200055b576200055b620001ed565b62000573816200056c8454620004ae565b84620004ea565b602080601f831160018114620005ab5760008415620005925750858301515b600019600386901b1c1916600185901b17855562000536565b600085815260208120601f198616915b82811015620005dc57888601518255948401946001909101908401620005bb565b5085821015620005fb5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60805160a051613119620006776000396000818161064901528181611b0c01528181611b810152611d8601526000818161050801528181610a78015281816110d201528181611349015281816116b401528181611eab01528181611fe5015261209e01526131196000f3fe60806040526004361061025c5760003560e01c8063715018a611610144578063bb0b6a53116100b6578063d045a0dc1161007a578063d045a0dc14610780578063d424388514610793578063dd62ed3e146107b3578063f2fde38b146107f9578063fc0c546a1461048c578063ff7bd03d1461081957600080fd5b8063bb0b6a53146106df578063bc70b3541461070c578063bd815db01461072c578063c7c7f5b31461073f578063ca5eb5e11461076057600080fd5b806395d89b411161010857806395d89b4114610622578063963efcaa146106375780639f68b9641461066b578063a9059cbb1461067f578063b731ea0a1461069f578063b98bd070146106bf57600080fd5b8063715018a6146105805780637d25a05e1461059557806382413eac146105d0578063857749b0146105f05780638da5cb5b1461060457600080fd5b806323b872dd116101dd57806352ae2879116101a157806352ae28791461048c5780635535d4611461049f5780635a0dfe4d146104bf5780635e280f11146104f65780636fc1b31e1461052a57806370a082311461054a57600080fd5b806323b872dd146103dd578063313ce567146103fd5780633400288b1461041f5780633b6f743b1461043f57806340c10f191461046c57600080fd5b8063134d4f2511610224578063134d4f2514610338578063156a0d0f1461036057806317442b701461038757806318160ddd146103a95780631f5e1334146103c857600080fd5b806306fdde0314610261578063095ea7b31461028c5780630d35b415146102bc578063111ecdad146102eb57806313137d6514610323575b600080fd5b34801561026d57600080fd5b50610276610839565b60405161028391906121fd565b60405180910390f35b34801561029857600080fd5b506102ac6102a7366004612225565b6108cb565b6040519015158152602001610283565b3480156102c857600080fd5b506102dc6102d7366004612269565b6108e5565b6040516102839392919061229d565b3480156102f757600080fd5b5060045461030b906001600160a01b031681565b6040516001600160a01b039091168152602001610283565b610336610331366004612390565b610a76565b005b34801561034457600080fd5b5061034d600281565b60405161ffff9091168152602001610283565b34801561036c57600080fd5b506040805162b9270b60e21b81526001602082015201610283565b34801561039357600080fd5b5060408051600181526002602082015201610283565b3480156103b557600080fd5b506007545b604051908152602001610283565b3480156103d457600080fd5b5061034d600181565b3480156103e957600080fd5b506102ac6103f836600461242f565b610b36565b34801561040957600080fd5b5060125b60405160ff9091168152602001610283565b34801561042b57600080fd5b5061033661043a366004612489565b610b5c565b34801561044b57600080fd5b5061045f61045a3660046124b3565b610b72565b6040516102839190612504565b34801561047857600080fd5b50610336610487366004612225565b610bd9565b34801561049857600080fd5b503061030b565b3480156104ab57600080fd5b506102766104ba36600461252d565b610be3565b3480156104cb57600080fd5b506102ac6104da366004612489565b63ffffffff919091166000908152600160205260409020541490565b34801561050257600080fd5b5061030b7f000000000000000000000000000000000000000000000000000000000000000081565b34801561053657600080fd5b50610336610545366004612560565b610c88565b34801561055657600080fd5b506103ba610565366004612560565b6001600160a01b031660009081526005602052604090205490565b34801561058c57600080fd5b50610336610ce5565b3480156105a157600080fd5b506105b86105b0366004612489565b600092915050565b6040516001600160401b039091168152602001610283565b3480156105dc57600080fd5b506102ac6105eb36600461257d565b610cf9565b3480156105fc57600080fd5b50600661040d565b34801561061057600080fd5b506000546001600160a01b031661030b565b34801561062e57600080fd5b50610276610d0e565b34801561064357600080fd5b506103ba7f000000000000000000000000000000000000000000000000000000000000000081565b34801561067757600080fd5b5060006102ac565b34801561068b57600080fd5b506102ac61069a366004612225565b610d1d565b3480156106ab57600080fd5b5060025461030b906001600160a01b031681565b3480156106cb57600080fd5b506103366106da366004612627565b610d2b565b3480156106eb57600080fd5b506103ba6106fa366004612668565b60016020526000908152604090205481565b34801561071857600080fd5b50610276610727366004612683565b610d45565b61033661073a366004612627565b610eed565b61075261074d3660046126e3565b611077565b604051610283929190612750565b34801561076c57600080fd5b5061033661077b366004612560565b6110ab565b61033661078e366004612390565b611131565b34801561079f57600080fd5b506103366107ae366004612560565b611160565b3480156107bf57600080fd5b506103ba6107ce3660046127a2565b6001600160a01b03918216600090815260066020908152604080832093909416825291909152205490565b34801561080557600080fd5b50610336610814366004612560565b6111b6565b34801561082557600080fd5b506102ac6108343660046127d0565b6111f4565b606060088054610848906127ec565b80601f0160208091040260200160405190810160405280929190818152602001828054610874906127ec565b80156108c15780601f10610896576101008083540402835291602001916108c1565b820191906000526020600020905b8154815290600101906020018083116108a457829003601f168201915b5050505050905090565b6000336108d981858561122a565b60019150505b92915050565b60408051808201909152600080825260208201526060610918604051806040016040528060008152602001600081525090565b600080306001600160a01b031663fc0c546a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610959573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061097d9190612820565b6001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109ba573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109de919061283d565b60408051808201825284815260208082018490528251600080825291810190935290975091925090610a33565b604080518082019091526000815260606020820152815260200190600190039081610a0b5790505b509350600080610a58604089013560608a0135610a5360208c018c612668565b61123c565b60408051808201909152918252602082015296989597505050505050565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163314610ac6576040516391ac5e4f60e01b81523360048201526024015b60405180910390fd5b60208701803590610ae090610adb908a612668565b611278565b14610b1e57610af26020880188612668565b60405163309afaf360e21b815263ffffffff909116600482015260208801356024820152604401610abd565b610b2d878787878787876112b4565b50505050505050565b600033610b4485828561141b565b610b4f85858561149a565b60019150505b9392505050565b610b646114f9565b610b6e8282611526565b5050565b60408051808201909152600080825260208201526000610ba260408501356060860135610a536020880188612668565b915050600080610bb2868461157b565b9092509050610bcf610bc76020880188612668565b83838861169e565b9695505050505050565b610b6e828261177f565b600360209081526000928352604080842090915290825290208054610c07906127ec565b80601f0160208091040260200160405190810160405280929190818152602001828054610c33906127ec565b8015610c805780601f10610c5557610100808354040283529160200191610c80565b820191906000526020600020905b815481529060010190602001808311610c6357829003601f168201915b505050505081565b610c906114f9565b600480546001600160a01b0319166001600160a01b0383169081179091556040519081527ff0be4f1e87349231d80c36b33f9e8639658eeaf474014dee15a3e6a4d4414197906020015b60405180910390a150565b610ced6114f9565b610cf760006117b5565b565b6001600160a01b03811630145b949350505050565b606060098054610848906127ec565b6000336108d981858561149a565b610d336114f9565b610b6e610d40828461290d565b611805565b63ffffffff8416600090815260036020908152604080832061ffff87168452909152812080546060929190610d79906127ec565b80601f0160208091040260200160405190810160405280929190818152602001828054610da5906127ec565b8015610df25780601f10610dc757610100808354040283529160200191610df2565b820191906000526020600020905b815481529060010190602001808311610dd557829003601f168201915b505050505090508051600003610e425783838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929450610d069350505050565b6000839003610e52579050610d06565b60028310610ed057610e9984848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061190c92505050565b80610ea78460028188612a22565b604051602001610eb993929190612a4c565b604051602081830303815290604052915050610d06565b8383604051639a6d49cd60e01b8152600401610abd929190612a9d565b60005b81811015610ff65736838383818110610f0b57610f0b612ab1565b9050602002810190610f1d9190612ac7565b9050610f50610f2f6020830183612668565b602083013563ffffffff919091166000908152600160205260409020541490565b610f5a5750610fee565b3063d045a0dc60c08301358360a0810135610f79610100830183612ae8565b610f8a610100890160e08a01612560565b610f986101208a018a612ae8565b6040518963ffffffff1660e01b8152600401610fba9796959493929190612b43565b6000604051808303818588803b158015610fd357600080fd5b505af1158015610fe7573d6000803e3d6000fd5b5050505050505b600101610ef0565b50336001600160a01b0316638e9e70996040518163ffffffff1660e01b8152600401600060405180830381865afa158015611035573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261105d9190810190612bc9565b604051638351eea760e01b8152600401610abd91906121fd565b61107f612166565b604080518082019091526000808252602082015261109e858585611938565b915091505b935093915050565b6110b36114f9565b60405163ca5eb5e160e01b81526001600160a01b0382811660048301527f0000000000000000000000000000000000000000000000000000000000000000169063ca5eb5e190602401600060405180830381600087803b15801561111657600080fd5b505af115801561112a573d6000803e3d6000fd5b5050505050565b3330146111515760405163029a949d60e31b815260040160405180910390fd5b610b2d87878787878787610b1e565b6111686114f9565b600280546001600160a01b0319166001600160a01b0383169081179091556040519081527fd48d879cef83a1c0bdda516f27b13ddb1b3f8bbac1c9e1511bb2a659c242776090602001610cda565b6111be6114f9565b6001600160a01b0381166111e857604051631e4fbdf760e01b815260006004820152602401610abd565b6111f1816117b5565b50565b600060208201803590600190839061120c9086612668565b63ffffffff1681526020810191909152604001600020541492915050565b6112378383836001611a33565b505050565b60008061124885611b08565b9150819050838110156110a3576040516371c4efed60e01b81526004810182905260248101859052604401610abd565b63ffffffff8116600090815260016020526040812054806108df5760405163f6ff4fb760e01b815263ffffffff84166004820152602401610abd565b60006112c66112c38787611b3f565b90565b905060006112f2826112e06112db8a8a611b57565b611b7a565b6112ed60208d018d612668565b611baf565b905060288611156113b957600061132f61131260608c0160408d01612c36565b61131f60208d018d612668565b8461132a8c8c611bd7565b611c22565b604051633e5ac80960e11b81529091506001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690637cb59012906113859086908d906000908790600401612c53565b600060405180830381600087803b15801561139f57600080fd5b505af11580156113b3573d6000803e3d6000fd5b50505050505b6001600160a01b038216887fefed6d3500546b29533b128a29e3a94d70788727f0507505ac12eaf2e578fd9c6113f260208d018d612668565b6040805163ffffffff9092168252602082018690520160405180910390a3505050505050505050565b6001600160a01b03838116600090815260066020908152604080832093861683529290522054600019811015611494578181101561148557604051637dc7a0d960e11b81526001600160a01b03841660048201526024810182905260448101839052606401610abd565b61149484848484036000611a33565b50505050565b6001600160a01b0383166114c457604051634b637e8f60e11b815260006004820152602401610abd565b6001600160a01b0382166114ee5760405163ec442f0560e01b815260006004820152602401610abd565b611237838383611c54565b6000546001600160a01b03163314610cf75760405163118cdaa760e01b8152336004820152602401610abd565b63ffffffff8216600081815260016020908152604091829020849055815192835282018390527f238399d427b947898edb290f5ff0f9109849b1c3ba196a42e35f00c50a54b98b910160405180910390a15050565b60608060006115d8856020013561159186611d7e565b61159e60a0890189612ae8565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611dd892505050565b90935090506000816115eb5760016115ee565b60025b905061160e6116006020880188612668565b8261072760808a018a612ae8565b6004549093506001600160a01b031680156116945760405163043a78eb60e01b81526001600160a01b0382169063043a78eb906116519088908890600401612c84565b602060405180830381865afa15801561166e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116929190612ca9565b505b5050509250929050565b60408051808201909152600080825260208201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663ddc28c586040518060a001604052808863ffffffff16815260200161170189611278565b8152602001878152602001868152602001851515815250306040518363ffffffff1660e01b8152600401611736929190612cc6565b6040805180830381865afa158015611752573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117769190612d6f565b95945050505050565b6001600160a01b0382166117a95760405163ec442f0560e01b815260006004820152602401610abd565b610b6e60008383611c54565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60005b81518110156118dc5761183782828151811061182657611826612ab1565b60200260200101516040015161190c565b81818151811061184957611849612ab1565b6020026020010151604001516003600084848151811061186b5761186b612ab1565b60200260200101516000015163ffffffff1663ffffffff16815260200190815260200160002060008484815181106118a5576118a5612ab1565b60200260200101516020015161ffff1661ffff16815260200190815260200160002090816118d39190612ddb565b50600101611808565b507fbe4864a8e820971c0247f5992e2da559595f7bf076a21cb5928d443d2a13b67481604051610cda9190612e9a565b600281015161ffff8116600314610b6e5781604051639a6d49cd60e01b8152600401610abd91906121fd565b611940612166565b604080518082019091526000808252602082015260008061197733604089013560608a013561197260208c018c612668565b611e52565b91509150600080611988898461157b565b90925090506119b461199d60208b018b612668565b83836119ae368d90038d018d612f25565b8b611e78565b60408051808201909152858152602080820186905282519298509096503391907f85496b760a4b7f8d66384b9df21b381f5d1b1e79f229a47aaf4c232edc2fe59a90611a02908d018d612668565b6040805163ffffffff909216825260208201899052810187905260600160405180910390a350505050935093915050565b6001600160a01b038416611a5d5760405163e602df0560e01b815260006004820152602401610abd565b6001600160a01b038316611a8757604051634a1406b160e11b815260006004820152602401610abd565b6001600160a01b038085166000908152600660209081526040808320938716835292905220829055801561149457826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92584604051611afa91815260200190565b60405180910390a350505050565b60007f0000000000000000000000000000000000000000000000000000000000000000611b358184612f6d565b6108df9190612f8f565b6000611b4e6020828486612a22565b610b5591612fa6565b6000611b67602860208486612a22565b611b7091612fc4565b60c01c9392505050565b60006108df7f00000000000000000000000000000000000000000000000000000000000000006001600160401b038416612f8f565b60006001600160a01b038416611bc55761dead93505b611bcf848461177f565b509092915050565b6060611be68260288186612a22565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929695505050505050565b606084848484604051602001611c3b9493929190612ff4565b6040516020818303038152906040529050949350505050565b6001600160a01b038316611c7f578060076000828254611c749190613043565b90915550611cf19050565b6001600160a01b03831660009081526005602052604090205481811015611cd25760405163391434e360e21b81526001600160a01b03851660048201526024810182905260448101839052606401610abd565b6001600160a01b03841660009081526005602052604090209082900390555b6001600160a01b038216611d0d57600780548290039055611d2c565b6001600160a01b03821660009081526005602052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051611d7191815260200190565b60405180910390a3505050565b600080611dab7f000000000000000000000000000000000000000000000000000000000000000084612f6d565b90506001600160401b038111156108df5760405163e2ce941360e01b815260048101829052602401610abd565b8051606090151580611e21578484604051602001611e0d92919091825260c01b6001600160c01b031916602082015260280190565b604051602081830303815290604052611e48565b84843385604051602001611e389493929190613056565b6040516020818303038152906040525b9150935093915050565b600080611e6085858561123c565b9092509050611e6f8683611f83565b94509492505050565b611e80612166565b6000611e8f8460000151611fb9565b602085015190915015611ea957611ea98460200151611fe1565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316632637a450826040518060a001604052808b63ffffffff168152602001611ef98c611278565b81526020018a815260200189815260200160008960200151111515815250866040518463ffffffff1660e01b8152600401611f35929190612cc6565b60806040518083038185885af1158015611f53573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190611f789190613099565b979650505050505050565b6001600160a01b038216611fad57604051634b637e8f60e11b815260006004820152602401610abd565b610b6e82600083611c54565b6000813414611fdd576040516304fb820960e51b8152346004820152602401610abd565b5090565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663e4fe1d946040518163ffffffff1660e01b8152600401602060405180830381865afa158015612041573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120659190612820565b90506001600160a01b03811661208e576040516329b99a9560e11b815260040160405180910390fd5b610b6e6001600160a01b038216337f0000000000000000000000000000000000000000000000000000000000000000856120cc8484848460016120f4565b61149457604051635274afe760e01b81526001600160a01b0385166004820152602401610abd565b6040516323b872dd60e01b60008181526001600160a01b038781166004528616602452604485905291602083606481808c5af192506001600051148316612154578383151615612147573d6000823e3d81fd5b6000883b113d1516831692505b60405250600060605295945050505050565b60405180606001604052806000801916815260200160006001600160401b031681526020016121a8604051806040016040528060008152602001600081525090565b905290565b60005b838110156121c85781810151838201526020016121b0565b50506000910152565b600081518084526121e98160208601602086016121ad565b601f01601f19169290920160200192915050565b602081526000610b5560208301846121d1565b6001600160a01b03811681146111f157600080fd5b6000806040838503121561223857600080fd5b823561224381612210565b946020939093013593505050565b600060e0828403121561226357600080fd5b50919050565b60006020828403121561227b57600080fd5b81356001600160401b0381111561229157600080fd5b610d0684828501612251565b8351815260208085015190820152600060a08201604060a0604085015281865180845260c08601915060c08160051b8701019350602080890160005b838110156123185788870360bf19018552815180518852830151838801879052612305878901826121d1565b97505093820193908201906001016122d9565b50508751606088015250505060208501516080850152509050610d06565b60006060828403121561226357600080fd5b60008083601f84011261235a57600080fd5b5081356001600160401b0381111561237157600080fd5b60208301915083602082850101111561238957600080fd5b9250929050565b600080600080600080600060e0888a0312156123ab57600080fd5b6123b58989612336565b96506060880135955060808801356001600160401b03808211156123d857600080fd5b6123e48b838c01612348565b909750955060a08a013591506123f982612210565b90935060c0890135908082111561240f57600080fd5b5061241c8a828b01612348565b989b979a50959850939692959293505050565b60008060006060848603121561244457600080fd5b833561244f81612210565b9250602084013561245f81612210565b929592945050506040919091013590565b803563ffffffff8116811461248457600080fd5b919050565b6000806040838503121561249c57600080fd5b61224383612470565b80151581146111f157600080fd5b600080604083850312156124c657600080fd5b82356001600160401b038111156124dc57600080fd5b6124e885828601612251565b92505060208301356124f9816124a5565b809150509250929050565b8151815260208083015190820152604081016108df565b803561ffff8116811461248457600080fd5b6000806040838503121561254057600080fd5b61254983612470565b91506125576020840161251b565b90509250929050565b60006020828403121561257257600080fd5b8135610b5581612210565b60008060008060a0858703121561259357600080fd5b61259d8686612336565b935060608501356001600160401b038111156125b857600080fd5b6125c487828801612348565b90945092505060808501356125d881612210565b939692955090935050565b60008083601f8401126125f557600080fd5b5081356001600160401b0381111561260c57600080fd5b6020830191508360208260051b850101111561238957600080fd5b6000806020838503121561263a57600080fd5b82356001600160401b0381111561265057600080fd5b61265c858286016125e3565b90969095509350505050565b60006020828403121561267a57600080fd5b610b5582612470565b6000806000806060858703121561269957600080fd5b6126a285612470565b93506126b06020860161251b565b925060408501356001600160401b038111156126cb57600080fd5b6126d787828801612348565b95989497509550505050565b600080600083850360808112156126f957600080fd5b84356001600160401b0381111561270f57600080fd5b61271b87828801612251565b9450506040601f198201121561273057600080fd5b50602084019150606084013561274581612210565b809150509250925092565b600060c082019050835182526001600160401b036020850151166020830152604084015161278b604084018280518252602090810151910152565b5082516080830152602083015160a0830152610b55565b600080604083850312156127b557600080fd5b82356127c081612210565b915060208301356124f981612210565b6000606082840312156127e257600080fd5b610b558383612336565b600181811c9082168061280057607f821691505b60208210810361226357634e487b7160e01b600052602260045260246000fd5b60006020828403121561283257600080fd5b8151610b5581612210565b60006020828403121561284f57600080fd5b5051919050565b634e487b7160e01b600052604160045260246000fd5b604051606081016001600160401b038111828210171561288e5761288e612856565b60405290565b604080519081016001600160401b038111828210171561288e5761288e612856565b604051601f8201601f191681016001600160401b03811182821017156128de576128de612856565b604052919050565b60006001600160401b038211156128ff576128ff612856565b50601f01601f191660200190565b60006001600160401b038084111561292757612927612856565b8360051b60206129388183016128b6565b86815291850191818101903684111561295057600080fd5b865b84811015612a165780358681111561296a5760008081fd5b8801606036829003121561297e5760008081fd5b61298661286c565b61298f82612470565b815261299c86830161251b565b86820152604080830135898111156129b45760008081fd5b929092019136601f8401126129c95760008081fd5b82356129dc6129d7826128e6565b6128b6565b81815236898387010111156129f15760008081fd5b818986018a830137600091810189019190915290820152845250918301918301612952565b50979650505050505050565b60008085851115612a3257600080fd5b83861115612a3f57600080fd5b5050820193919092039150565b60008451612a5e8184602089016121ad565b8201838582376000930192835250909392505050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b602081526000610d06602083018486612a74565b634e487b7160e01b600052603260045260246000fd5b6000823561013e19833603018112612ade57600080fd5b9190910192915050565b6000808335601e19843603018112612aff57600080fd5b8301803591506001600160401b03821115612b1957600080fd5b60200191503681900382131561238957600080fd5b6001600160401b03811681146111f157600080fd5b63ffffffff612b5189612470565b1681526020880135602082015260006040890135612b6e81612b2e565b6001600160401b03811660408401525087606083015260e06080830152612b9960e083018789612a74565b6001600160a01b03861660a084015282810360c0840152612bbb818587612a74565b9a9950505050505050505050565b600060208284031215612bdb57600080fd5b81516001600160401b03811115612bf157600080fd5b8201601f81018413612c0257600080fd5b8051612c106129d7826128e6565b818152856020838501011115612c2557600080fd5b6117768260208301602086016121ad565b600060208284031215612c4857600080fd5b8135610b5581612b2e565b60018060a01b038516815283602082015261ffff83166040820152608060608201526000610bcf60808301846121d1565b604081526000612c9760408301856121d1565b828103602084015261177681856121d1565b600060208284031215612cbb57600080fd5b8151610b55816124a5565b6040815263ffffffff8351166040820152602083015160608201526000604084015160a06080840152612cfc60e08401826121d1565b90506060850151603f198483030160a0850152612d1982826121d1565b60809690960151151560c08501525050506001600160a01b039190911660209091015290565b600060408284031215612d5157600080fd5b612d59612894565b9050815181526020820151602082015292915050565b600060408284031215612d8157600080fd5b610b558383612d3f565b601f821115611237576000816000526020600020601f850160051c81016020861015612db45750805b601f850160051c820191505b81811015612dd357828155600101612dc0565b505050505050565b81516001600160401b03811115612df457612df4612856565b612e0881612e0284546127ec565b84612d8b565b602080601f831160018114612e3d5760008415612e255750858301515b600019600386901b1c1916600185901b178555612dd3565b600085815260208120601f198616915b82811015612e6c57888601518255948401946001909101908401612e4d565b5085821015612e8a5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b600060208083018184528085518083526040925060408601915060408160051b87010184880160005b83811015612f1757888303603f190185528151805163ffffffff1684528781015161ffff16888501528601516060878501819052612f03818601836121d1565b968901969450505090860190600101612ec3565b509098975050505050505050565b600060408284031215612f3757600080fd5b612f3f612894565b82358152602083013560208201528091505092915050565b634e487b7160e01b600052601160045260246000fd5b600082612f8a57634e487b7160e01b600052601260045260246000fd5b500490565b80820281158282048414176108df576108df612f57565b803560208310156108df57600019602084900360031b1b1692915050565b6001600160c01b03198135818116916008851015612fec5780818660080360031b1b83161692505b505092915050565b6001600160401b0360c01b8560c01b16815263ffffffff60e01b8460e01b16600882015282600c8201526000825161303381602c8501602087016121ad565b91909101602c0195945050505050565b808201808211156108df576108df612f57565b8481526001600160401b0360c01b8460c01b166020820152826028820152600082516130898160488501602087016121ad565b9190910160480195945050505050565b6000608082840312156130ab57600080fd5b6130b361286c565b8251815260208301516130c581612b2e565b60208201526130d78460408501612d3f565b6040820152939250505056fea2646970667358221220aa404cddf55107bb4ef2c6beb0224c8f9f889ea8535030db0d36c362777d7cae64736f6c63430008160033", + "deployedBytecode": "0x60806040526004361061025c5760003560e01c8063715018a611610144578063bb0b6a53116100b6578063d045a0dc1161007a578063d045a0dc14610780578063d424388514610793578063dd62ed3e146107b3578063f2fde38b146107f9578063fc0c546a1461048c578063ff7bd03d1461081957600080fd5b8063bb0b6a53146106df578063bc70b3541461070c578063bd815db01461072c578063c7c7f5b31461073f578063ca5eb5e11461076057600080fd5b806395d89b411161010857806395d89b4114610622578063963efcaa146106375780639f68b9641461066b578063a9059cbb1461067f578063b731ea0a1461069f578063b98bd070146106bf57600080fd5b8063715018a6146105805780637d25a05e1461059557806382413eac146105d0578063857749b0146105f05780638da5cb5b1461060457600080fd5b806323b872dd116101dd57806352ae2879116101a157806352ae28791461048c5780635535d4611461049f5780635a0dfe4d146104bf5780635e280f11146104f65780636fc1b31e1461052a57806370a082311461054a57600080fd5b806323b872dd146103dd578063313ce567146103fd5780633400288b1461041f5780633b6f743b1461043f57806340c10f191461046c57600080fd5b8063134d4f2511610224578063134d4f2514610338578063156a0d0f1461036057806317442b701461038757806318160ddd146103a95780631f5e1334146103c857600080fd5b806306fdde0314610261578063095ea7b31461028c5780630d35b415146102bc578063111ecdad146102eb57806313137d6514610323575b600080fd5b34801561026d57600080fd5b50610276610839565b60405161028391906121fd565b60405180910390f35b34801561029857600080fd5b506102ac6102a7366004612225565b6108cb565b6040519015158152602001610283565b3480156102c857600080fd5b506102dc6102d7366004612269565b6108e5565b6040516102839392919061229d565b3480156102f757600080fd5b5060045461030b906001600160a01b031681565b6040516001600160a01b039091168152602001610283565b610336610331366004612390565b610a76565b005b34801561034457600080fd5b5061034d600281565b60405161ffff9091168152602001610283565b34801561036c57600080fd5b506040805162b9270b60e21b81526001602082015201610283565b34801561039357600080fd5b5060408051600181526002602082015201610283565b3480156103b557600080fd5b506007545b604051908152602001610283565b3480156103d457600080fd5b5061034d600181565b3480156103e957600080fd5b506102ac6103f836600461242f565b610b36565b34801561040957600080fd5b5060125b60405160ff9091168152602001610283565b34801561042b57600080fd5b5061033661043a366004612489565b610b5c565b34801561044b57600080fd5b5061045f61045a3660046124b3565b610b72565b6040516102839190612504565b34801561047857600080fd5b50610336610487366004612225565b610bd9565b34801561049857600080fd5b503061030b565b3480156104ab57600080fd5b506102766104ba36600461252d565b610be3565b3480156104cb57600080fd5b506102ac6104da366004612489565b63ffffffff919091166000908152600160205260409020541490565b34801561050257600080fd5b5061030b7f000000000000000000000000000000000000000000000000000000000000000081565b34801561053657600080fd5b50610336610545366004612560565b610c88565b34801561055657600080fd5b506103ba610565366004612560565b6001600160a01b031660009081526005602052604090205490565b34801561058c57600080fd5b50610336610ce5565b3480156105a157600080fd5b506105b86105b0366004612489565b600092915050565b6040516001600160401b039091168152602001610283565b3480156105dc57600080fd5b506102ac6105eb36600461257d565b610cf9565b3480156105fc57600080fd5b50600661040d565b34801561061057600080fd5b506000546001600160a01b031661030b565b34801561062e57600080fd5b50610276610d0e565b34801561064357600080fd5b506103ba7f000000000000000000000000000000000000000000000000000000000000000081565b34801561067757600080fd5b5060006102ac565b34801561068b57600080fd5b506102ac61069a366004612225565b610d1d565b3480156106ab57600080fd5b5060025461030b906001600160a01b031681565b3480156106cb57600080fd5b506103366106da366004612627565b610d2b565b3480156106eb57600080fd5b506103ba6106fa366004612668565b60016020526000908152604090205481565b34801561071857600080fd5b50610276610727366004612683565b610d45565b61033661073a366004612627565b610eed565b61075261074d3660046126e3565b611077565b604051610283929190612750565b34801561076c57600080fd5b5061033661077b366004612560565b6110ab565b61033661078e366004612390565b611131565b34801561079f57600080fd5b506103366107ae366004612560565b611160565b3480156107bf57600080fd5b506103ba6107ce3660046127a2565b6001600160a01b03918216600090815260066020908152604080832093909416825291909152205490565b34801561080557600080fd5b50610336610814366004612560565b6111b6565b34801561082557600080fd5b506102ac6108343660046127d0565b6111f4565b606060088054610848906127ec565b80601f0160208091040260200160405190810160405280929190818152602001828054610874906127ec565b80156108c15780601f10610896576101008083540402835291602001916108c1565b820191906000526020600020905b8154815290600101906020018083116108a457829003601f168201915b5050505050905090565b6000336108d981858561122a565b60019150505b92915050565b60408051808201909152600080825260208201526060610918604051806040016040528060008152602001600081525090565b600080306001600160a01b031663fc0c546a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610959573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061097d9190612820565b6001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109ba573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109de919061283d565b60408051808201825284815260208082018490528251600080825291810190935290975091925090610a33565b604080518082019091526000815260606020820152815260200190600190039081610a0b5790505b509350600080610a58604089013560608a0135610a5360208c018c612668565b61123c565b60408051808201909152918252602082015296989597505050505050565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163314610ac6576040516391ac5e4f60e01b81523360048201526024015b60405180910390fd5b60208701803590610ae090610adb908a612668565b611278565b14610b1e57610af26020880188612668565b60405163309afaf360e21b815263ffffffff909116600482015260208801356024820152604401610abd565b610b2d878787878787876112b4565b50505050505050565b600033610b4485828561141b565b610b4f85858561149a565b60019150505b9392505050565b610b646114f9565b610b6e8282611526565b5050565b60408051808201909152600080825260208201526000610ba260408501356060860135610a536020880188612668565b915050600080610bb2868461157b565b9092509050610bcf610bc76020880188612668565b83838861169e565b9695505050505050565b610b6e828261177f565b600360209081526000928352604080842090915290825290208054610c07906127ec565b80601f0160208091040260200160405190810160405280929190818152602001828054610c33906127ec565b8015610c805780601f10610c5557610100808354040283529160200191610c80565b820191906000526020600020905b815481529060010190602001808311610c6357829003601f168201915b505050505081565b610c906114f9565b600480546001600160a01b0319166001600160a01b0383169081179091556040519081527ff0be4f1e87349231d80c36b33f9e8639658eeaf474014dee15a3e6a4d4414197906020015b60405180910390a150565b610ced6114f9565b610cf760006117b5565b565b6001600160a01b03811630145b949350505050565b606060098054610848906127ec565b6000336108d981858561149a565b610d336114f9565b610b6e610d40828461290d565b611805565b63ffffffff8416600090815260036020908152604080832061ffff87168452909152812080546060929190610d79906127ec565b80601f0160208091040260200160405190810160405280929190818152602001828054610da5906127ec565b8015610df25780601f10610dc757610100808354040283529160200191610df2565b820191906000526020600020905b815481529060010190602001808311610dd557829003601f168201915b505050505090508051600003610e425783838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929450610d069350505050565b6000839003610e52579050610d06565b60028310610ed057610e9984848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061190c92505050565b80610ea78460028188612a22565b604051602001610eb993929190612a4c565b604051602081830303815290604052915050610d06565b8383604051639a6d49cd60e01b8152600401610abd929190612a9d565b60005b81811015610ff65736838383818110610f0b57610f0b612ab1565b9050602002810190610f1d9190612ac7565b9050610f50610f2f6020830183612668565b602083013563ffffffff919091166000908152600160205260409020541490565b610f5a5750610fee565b3063d045a0dc60c08301358360a0810135610f79610100830183612ae8565b610f8a610100890160e08a01612560565b610f986101208a018a612ae8565b6040518963ffffffff1660e01b8152600401610fba9796959493929190612b43565b6000604051808303818588803b158015610fd357600080fd5b505af1158015610fe7573d6000803e3d6000fd5b5050505050505b600101610ef0565b50336001600160a01b0316638e9e70996040518163ffffffff1660e01b8152600401600060405180830381865afa158015611035573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261105d9190810190612bc9565b604051638351eea760e01b8152600401610abd91906121fd565b61107f612166565b604080518082019091526000808252602082015261109e858585611938565b915091505b935093915050565b6110b36114f9565b60405163ca5eb5e160e01b81526001600160a01b0382811660048301527f0000000000000000000000000000000000000000000000000000000000000000169063ca5eb5e190602401600060405180830381600087803b15801561111657600080fd5b505af115801561112a573d6000803e3d6000fd5b5050505050565b3330146111515760405163029a949d60e31b815260040160405180910390fd5b610b2d87878787878787610b1e565b6111686114f9565b600280546001600160a01b0319166001600160a01b0383169081179091556040519081527fd48d879cef83a1c0bdda516f27b13ddb1b3f8bbac1c9e1511bb2a659c242776090602001610cda565b6111be6114f9565b6001600160a01b0381166111e857604051631e4fbdf760e01b815260006004820152602401610abd565b6111f1816117b5565b50565b600060208201803590600190839061120c9086612668565b63ffffffff1681526020810191909152604001600020541492915050565b6112378383836001611a33565b505050565b60008061124885611b08565b9150819050838110156110a3576040516371c4efed60e01b81526004810182905260248101859052604401610abd565b63ffffffff8116600090815260016020526040812054806108df5760405163f6ff4fb760e01b815263ffffffff84166004820152602401610abd565b60006112c66112c38787611b3f565b90565b905060006112f2826112e06112db8a8a611b57565b611b7a565b6112ed60208d018d612668565b611baf565b905060288611156113b957600061132f61131260608c0160408d01612c36565b61131f60208d018d612668565b8461132a8c8c611bd7565b611c22565b604051633e5ac80960e11b81529091506001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690637cb59012906113859086908d906000908790600401612c53565b600060405180830381600087803b15801561139f57600080fd5b505af11580156113b3573d6000803e3d6000fd5b50505050505b6001600160a01b038216887fefed6d3500546b29533b128a29e3a94d70788727f0507505ac12eaf2e578fd9c6113f260208d018d612668565b6040805163ffffffff9092168252602082018690520160405180910390a3505050505050505050565b6001600160a01b03838116600090815260066020908152604080832093861683529290522054600019811015611494578181101561148557604051637dc7a0d960e11b81526001600160a01b03841660048201526024810182905260448101839052606401610abd565b61149484848484036000611a33565b50505050565b6001600160a01b0383166114c457604051634b637e8f60e11b815260006004820152602401610abd565b6001600160a01b0382166114ee5760405163ec442f0560e01b815260006004820152602401610abd565b611237838383611c54565b6000546001600160a01b03163314610cf75760405163118cdaa760e01b8152336004820152602401610abd565b63ffffffff8216600081815260016020908152604091829020849055815192835282018390527f238399d427b947898edb290f5ff0f9109849b1c3ba196a42e35f00c50a54b98b910160405180910390a15050565b60608060006115d8856020013561159186611d7e565b61159e60a0890189612ae8565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611dd892505050565b90935090506000816115eb5760016115ee565b60025b905061160e6116006020880188612668565b8261072760808a018a612ae8565b6004549093506001600160a01b031680156116945760405163043a78eb60e01b81526001600160a01b0382169063043a78eb906116519088908890600401612c84565b602060405180830381865afa15801561166e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116929190612ca9565b505b5050509250929050565b60408051808201909152600080825260208201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663ddc28c586040518060a001604052808863ffffffff16815260200161170189611278565b8152602001878152602001868152602001851515815250306040518363ffffffff1660e01b8152600401611736929190612cc6565b6040805180830381865afa158015611752573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117769190612d6f565b95945050505050565b6001600160a01b0382166117a95760405163ec442f0560e01b815260006004820152602401610abd565b610b6e60008383611c54565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60005b81518110156118dc5761183782828151811061182657611826612ab1565b60200260200101516040015161190c565b81818151811061184957611849612ab1565b6020026020010151604001516003600084848151811061186b5761186b612ab1565b60200260200101516000015163ffffffff1663ffffffff16815260200190815260200160002060008484815181106118a5576118a5612ab1565b60200260200101516020015161ffff1661ffff16815260200190815260200160002090816118d39190612ddb565b50600101611808565b507fbe4864a8e820971c0247f5992e2da559595f7bf076a21cb5928d443d2a13b67481604051610cda9190612e9a565b600281015161ffff8116600314610b6e5781604051639a6d49cd60e01b8152600401610abd91906121fd565b611940612166565b604080518082019091526000808252602082015260008061197733604089013560608a013561197260208c018c612668565b611e52565b91509150600080611988898461157b565b90925090506119b461199d60208b018b612668565b83836119ae368d90038d018d612f25565b8b611e78565b60408051808201909152858152602080820186905282519298509096503391907f85496b760a4b7f8d66384b9df21b381f5d1b1e79f229a47aaf4c232edc2fe59a90611a02908d018d612668565b6040805163ffffffff909216825260208201899052810187905260600160405180910390a350505050935093915050565b6001600160a01b038416611a5d5760405163e602df0560e01b815260006004820152602401610abd565b6001600160a01b038316611a8757604051634a1406b160e11b815260006004820152602401610abd565b6001600160a01b038085166000908152600660209081526040808320938716835292905220829055801561149457826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92584604051611afa91815260200190565b60405180910390a350505050565b60007f0000000000000000000000000000000000000000000000000000000000000000611b358184612f6d565b6108df9190612f8f565b6000611b4e6020828486612a22565b610b5591612fa6565b6000611b67602860208486612a22565b611b7091612fc4565b60c01c9392505050565b60006108df7f00000000000000000000000000000000000000000000000000000000000000006001600160401b038416612f8f565b60006001600160a01b038416611bc55761dead93505b611bcf848461177f565b509092915050565b6060611be68260288186612a22565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929695505050505050565b606084848484604051602001611c3b9493929190612ff4565b6040516020818303038152906040529050949350505050565b6001600160a01b038316611c7f578060076000828254611c749190613043565b90915550611cf19050565b6001600160a01b03831660009081526005602052604090205481811015611cd25760405163391434e360e21b81526001600160a01b03851660048201526024810182905260448101839052606401610abd565b6001600160a01b03841660009081526005602052604090209082900390555b6001600160a01b038216611d0d57600780548290039055611d2c565b6001600160a01b03821660009081526005602052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051611d7191815260200190565b60405180910390a3505050565b600080611dab7f000000000000000000000000000000000000000000000000000000000000000084612f6d565b90506001600160401b038111156108df5760405163e2ce941360e01b815260048101829052602401610abd565b8051606090151580611e21578484604051602001611e0d92919091825260c01b6001600160c01b031916602082015260280190565b604051602081830303815290604052611e48565b84843385604051602001611e389493929190613056565b6040516020818303038152906040525b9150935093915050565b600080611e6085858561123c565b9092509050611e6f8683611f83565b94509492505050565b611e80612166565b6000611e8f8460000151611fb9565b602085015190915015611ea957611ea98460200151611fe1565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316632637a450826040518060a001604052808b63ffffffff168152602001611ef98c611278565b81526020018a815260200189815260200160008960200151111515815250866040518463ffffffff1660e01b8152600401611f35929190612cc6565b60806040518083038185885af1158015611f53573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190611f789190613099565b979650505050505050565b6001600160a01b038216611fad57604051634b637e8f60e11b815260006004820152602401610abd565b610b6e82600083611c54565b6000813414611fdd576040516304fb820960e51b8152346004820152602401610abd565b5090565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663e4fe1d946040518163ffffffff1660e01b8152600401602060405180830381865afa158015612041573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120659190612820565b90506001600160a01b03811661208e576040516329b99a9560e11b815260040160405180910390fd5b610b6e6001600160a01b038216337f0000000000000000000000000000000000000000000000000000000000000000856120cc8484848460016120f4565b61149457604051635274afe760e01b81526001600160a01b0385166004820152602401610abd565b6040516323b872dd60e01b60008181526001600160a01b038781166004528616602452604485905291602083606481808c5af192506001600051148316612154578383151615612147573d6000823e3d81fd5b6000883b113d1516831692505b60405250600060605295945050505050565b60405180606001604052806000801916815260200160006001600160401b031681526020016121a8604051806040016040528060008152602001600081525090565b905290565b60005b838110156121c85781810151838201526020016121b0565b50506000910152565b600081518084526121e98160208601602086016121ad565b601f01601f19169290920160200192915050565b602081526000610b5560208301846121d1565b6001600160a01b03811681146111f157600080fd5b6000806040838503121561223857600080fd5b823561224381612210565b946020939093013593505050565b600060e0828403121561226357600080fd5b50919050565b60006020828403121561227b57600080fd5b81356001600160401b0381111561229157600080fd5b610d0684828501612251565b8351815260208085015190820152600060a08201604060a0604085015281865180845260c08601915060c08160051b8701019350602080890160005b838110156123185788870360bf19018552815180518852830151838801879052612305878901826121d1565b97505093820193908201906001016122d9565b50508751606088015250505060208501516080850152509050610d06565b60006060828403121561226357600080fd5b60008083601f84011261235a57600080fd5b5081356001600160401b0381111561237157600080fd5b60208301915083602082850101111561238957600080fd5b9250929050565b600080600080600080600060e0888a0312156123ab57600080fd5b6123b58989612336565b96506060880135955060808801356001600160401b03808211156123d857600080fd5b6123e48b838c01612348565b909750955060a08a013591506123f982612210565b90935060c0890135908082111561240f57600080fd5b5061241c8a828b01612348565b989b979a50959850939692959293505050565b60008060006060848603121561244457600080fd5b833561244f81612210565b9250602084013561245f81612210565b929592945050506040919091013590565b803563ffffffff8116811461248457600080fd5b919050565b6000806040838503121561249c57600080fd5b61224383612470565b80151581146111f157600080fd5b600080604083850312156124c657600080fd5b82356001600160401b038111156124dc57600080fd5b6124e885828601612251565b92505060208301356124f9816124a5565b809150509250929050565b8151815260208083015190820152604081016108df565b803561ffff8116811461248457600080fd5b6000806040838503121561254057600080fd5b61254983612470565b91506125576020840161251b565b90509250929050565b60006020828403121561257257600080fd5b8135610b5581612210565b60008060008060a0858703121561259357600080fd5b61259d8686612336565b935060608501356001600160401b038111156125b857600080fd5b6125c487828801612348565b90945092505060808501356125d881612210565b939692955090935050565b60008083601f8401126125f557600080fd5b5081356001600160401b0381111561260c57600080fd5b6020830191508360208260051b850101111561238957600080fd5b6000806020838503121561263a57600080fd5b82356001600160401b0381111561265057600080fd5b61265c858286016125e3565b90969095509350505050565b60006020828403121561267a57600080fd5b610b5582612470565b6000806000806060858703121561269957600080fd5b6126a285612470565b93506126b06020860161251b565b925060408501356001600160401b038111156126cb57600080fd5b6126d787828801612348565b95989497509550505050565b600080600083850360808112156126f957600080fd5b84356001600160401b0381111561270f57600080fd5b61271b87828801612251565b9450506040601f198201121561273057600080fd5b50602084019150606084013561274581612210565b809150509250925092565b600060c082019050835182526001600160401b036020850151166020830152604084015161278b604084018280518252602090810151910152565b5082516080830152602083015160a0830152610b55565b600080604083850312156127b557600080fd5b82356127c081612210565b915060208301356124f981612210565b6000606082840312156127e257600080fd5b610b558383612336565b600181811c9082168061280057607f821691505b60208210810361226357634e487b7160e01b600052602260045260246000fd5b60006020828403121561283257600080fd5b8151610b5581612210565b60006020828403121561284f57600080fd5b5051919050565b634e487b7160e01b600052604160045260246000fd5b604051606081016001600160401b038111828210171561288e5761288e612856565b60405290565b604080519081016001600160401b038111828210171561288e5761288e612856565b604051601f8201601f191681016001600160401b03811182821017156128de576128de612856565b604052919050565b60006001600160401b038211156128ff576128ff612856565b50601f01601f191660200190565b60006001600160401b038084111561292757612927612856565b8360051b60206129388183016128b6565b86815291850191818101903684111561295057600080fd5b865b84811015612a165780358681111561296a5760008081fd5b8801606036829003121561297e5760008081fd5b61298661286c565b61298f82612470565b815261299c86830161251b565b86820152604080830135898111156129b45760008081fd5b929092019136601f8401126129c95760008081fd5b82356129dc6129d7826128e6565b6128b6565b81815236898387010111156129f15760008081fd5b818986018a830137600091810189019190915290820152845250918301918301612952565b50979650505050505050565b60008085851115612a3257600080fd5b83861115612a3f57600080fd5b5050820193919092039150565b60008451612a5e8184602089016121ad565b8201838582376000930192835250909392505050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b602081526000610d06602083018486612a74565b634e487b7160e01b600052603260045260246000fd5b6000823561013e19833603018112612ade57600080fd5b9190910192915050565b6000808335601e19843603018112612aff57600080fd5b8301803591506001600160401b03821115612b1957600080fd5b60200191503681900382131561238957600080fd5b6001600160401b03811681146111f157600080fd5b63ffffffff612b5189612470565b1681526020880135602082015260006040890135612b6e81612b2e565b6001600160401b03811660408401525087606083015260e06080830152612b9960e083018789612a74565b6001600160a01b03861660a084015282810360c0840152612bbb818587612a74565b9a9950505050505050505050565b600060208284031215612bdb57600080fd5b81516001600160401b03811115612bf157600080fd5b8201601f81018413612c0257600080fd5b8051612c106129d7826128e6565b818152856020838501011115612c2557600080fd5b6117768260208301602086016121ad565b600060208284031215612c4857600080fd5b8135610b5581612b2e565b60018060a01b038516815283602082015261ffff83166040820152608060608201526000610bcf60808301846121d1565b604081526000612c9760408301856121d1565b828103602084015261177681856121d1565b600060208284031215612cbb57600080fd5b8151610b55816124a5565b6040815263ffffffff8351166040820152602083015160608201526000604084015160a06080840152612cfc60e08401826121d1565b90506060850151603f198483030160a0850152612d1982826121d1565b60809690960151151560c08501525050506001600160a01b039190911660209091015290565b600060408284031215612d5157600080fd5b612d59612894565b9050815181526020820151602082015292915050565b600060408284031215612d8157600080fd5b610b558383612d3f565b601f821115611237576000816000526020600020601f850160051c81016020861015612db45750805b601f850160051c820191505b81811015612dd357828155600101612dc0565b505050505050565b81516001600160401b03811115612df457612df4612856565b612e0881612e0284546127ec565b84612d8b565b602080601f831160018114612e3d5760008415612e255750858301515b600019600386901b1c1916600185901b178555612dd3565b600085815260208120601f198616915b82811015612e6c57888601518255948401946001909101908401612e4d565b5085821015612e8a5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b600060208083018184528085518083526040925060408601915060408160051b87010184880160005b83811015612f1757888303603f190185528151805163ffffffff1684528781015161ffff16888501528601516060878501819052612f03818601836121d1565b968901969450505090860190600101612ec3565b509098975050505050505050565b600060408284031215612f3757600080fd5b612f3f612894565b82358152602083013560208201528091505092915050565b634e487b7160e01b600052601160045260246000fd5b600082612f8a57634e487b7160e01b600052601260045260246000fd5b500490565b80820281158282048414176108df576108df612f57565b803560208310156108df57600019602084900360031b1b1692915050565b6001600160c01b03198135818116916008851015612fec5780818660080360031b1b83161692505b505092915050565b6001600160401b0360c01b8560c01b16815263ffffffff60e01b8460e01b16600882015282600c8201526000825161303381602c8501602087016121ad565b91909101602c0195945050505050565b808201808211156108df576108df612f57565b8481526001600160401b0360c01b8460c01b166020820152826028820152600082516130898160488501602087016121ad565b9190910160480195945050505050565b6000608082840312156130ab57600080fd5b6130b361286c565b8251815260208301516130c581612b2e565b60208201526130d78460408501612d3f565b6040820152939250505056fea2646970667358221220aa404cddf55107bb4ef2c6beb0224c8f9f889ea8535030db0d36c362777d7cae64736f6c63430008160033", + "devdoc": { + "errors": { + "ERC20InsufficientAllowance(address,uint256,uint256)": [ + { + "details": "Indicates a failure with the `spender`’s `allowance`. Used in transfers.", + "params": { + "allowance": "Amount of tokens a `spender` is allowed to operate with.", + "needed": "Minimum amount required to perform a transfer.", + "spender": "Address that may be allowed to operate on tokens without being their owner." + } + } + ], + "ERC20InsufficientBalance(address,uint256,uint256)": [ + { + "details": "Indicates an error related to the current `balance` of a `sender`. Used in transfers.", + "params": { + "balance": "Current balance for the interacting account.", + "needed": "Minimum amount required to perform a transfer.", + "sender": "Address whose tokens are being transferred." + } + } + ], + "ERC20InvalidApprover(address)": [ + { + "details": "Indicates a failure with the `approver` of a token to be approved. Used in approvals.", + "params": { + "approver": "Address initiating an approval operation." + } + } + ], + "ERC20InvalidReceiver(address)": [ + { + "details": "Indicates a failure with the token `receiver`. Used in transfers.", + "params": { + "receiver": "Address to which tokens are being transferred." + } + } + ], + "ERC20InvalidSender(address)": [ + { + "details": "Indicates a failure with the token `sender`. Used in transfers.", + "params": { + "sender": "Address whose tokens are being transferred." + } + } + ], + "ERC20InvalidSpender(address)": [ + { + "details": "Indicates a failure with the `spender` to be approved. Used in approvals.", + "params": { + "spender": "Address that may be allowed to operate on tokens without being their owner." + } + } + ], + "OwnableInvalidOwner(address)": [ + { + "details": "The owner is not a valid owner account. (eg. `address(0)`)" + } + ], + "OwnableUnauthorizedAccount(address)": [ + { + "details": "The caller account is not authorized to perform an operation." + } + ], + "SafeERC20FailedOperation(address)": [ + { + "details": "An operation with an ERC-20 token failed." + } + ] + }, + "events": { + "Approval(address,address,uint256)": { + "details": "Emitted when the allowance of a `spender` for an `owner` is set by a call to {approve}. `value` is the new allowance." + }, + "PreCrimeSet(address)": { + "details": "Emitted when the preCrime contract address is set.", + "params": { + "preCrimeAddress": "The address of the preCrime contract." + } + }, + "Transfer(address,address,uint256)": { + "details": "Emitted when `value` tokens are moved from one account (`from`) to another (`to`). Note that `value` may be zero." + } + }, + "kind": "dev", + "methods": { + "allowInitializePath((uint32,bytes32,uint64))": { + "details": "This indicates to the endpoint that the OApp has enabled msgs for this particular path to be received.This defaults to assuming if a peer has been set, its initialized. Can be overridden by the OApp if there is other logic to determine this.", + "params": { + "origin": "The origin information containing the source endpoint and sender address." + }, + "returns": { + "_0": "Whether the path has been initialized." + } + }, + "allowance(address,address)": { + "details": "Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` through {transferFrom}. This is zero by default. This value changes when {approve} or {transferFrom} are called." + }, + "approvalRequired()": { + "details": "In the case of OFT where the contract IS the token, approval is NOT required.", + "returns": { + "_0": "requiresApproval Needs approval of the underlying token implementation." + } + }, + "approve(address,uint256)": { + "details": "See {IERC20-approve}. NOTE: If `value` is the maximum `uint256`, the allowance is not updated on `transferFrom`. This is semantically equivalent to an infinite approval. Requirements: - `spender` cannot be the zero address." + }, + "balanceOf(address)": { + "details": "Returns the value of tokens owned by `account`." + }, + "combineOptions(uint32,uint16,bytes)": { + "details": "If there is an enforced lzReceive option: - {gasLimit: 200k, msg.value: 1 ether} AND a caller supplies a lzReceive option: {gasLimit: 100k, msg.value: 0.5 ether} - The resulting options will be {gasLimit: 300k, msg.value: 1.5 ether} when the message is executed on the remote lzReceive() function.This presence of duplicated options is handled off-chain in the verifier/executor.", + "params": { + "_eid": "The endpoint ID.", + "_extraOptions": "Additional options passed by the caller.", + "_msgType": "The OAPP message type." + }, + "returns": { + "_0": "options The combination of caller specified options AND enforced options." + } + }, + "decimals()": { + "details": "Returns the number of decimals used to get its user representation. For example, if `decimals` equals `2`, a balance of `505` tokens should be displayed to a user as `5.05` (`505 / 10 ** 2`). Tokens usually opt for a value of 18, imitating the relationship between Ether and Wei. This is the default value returned by this function, unless it's overridden. NOTE: This information is only used for _display_ purposes: it in no way affects any of the arithmetic of the contract, including {IERC20-balanceOf} and {IERC20-transfer}." + }, + "isComposeMsgSender((uint32,bytes32,uint64),bytes,address)": { + "details": "_origin The origin information containing the source endpoint and sender address. - srcEid: The source chain endpoint ID. - sender: The sender address on the src chain. - nonce: The nonce of the message._message The lzReceive payload.Applications can optionally choose to implement separate composeMsg senders that are NOT the bridging layer.The default sender IS the OAppReceiver implementer.", + "params": { + "_sender": "The sender address." + }, + "returns": { + "_0": "isSender Is a valid sender." + } + }, + "isPeer(uint32,bytes32)": { + "details": "Check if the peer is considered 'trusted' by the OApp.Enables OAppPreCrimeSimulator to check whether a potential Inbound Packet is from a trusted source.", + "params": { + "_eid": "The endpoint ID to check.", + "_peer": "The peer to check." + }, + "returns": { + "_0": "Whether the peer passed is considered 'trusted' by the OApp." + } + }, + "lzReceive((uint32,bytes32,uint64),bytes32,bytes,address,bytes)": { + "details": "Entry point for receiving messages or packets from the endpoint.Entry point for receiving msg/packet from the LayerZero endpoint.", + "params": { + "_executor": "The address of the executor for the received message.", + "_extraData": "Additional arbitrary data provided by the corresponding executor.", + "_guid": "The unique identifier for the received LayerZero message.", + "_message": "The payload of the received message.", + "_origin": "The origin information containing the source endpoint and sender address. - srcEid: The source chain endpoint ID. - sender: The sender address on the src chain. - nonce: The nonce of the message." + } + }, + "lzReceiveAndRevert(((uint32,bytes32,uint64),uint32,address,bytes32,uint256,address,bytes,bytes)[])": { + "details": "Interface for pre-crime simulations. Always reverts at the end with the simulation results.WARNING: MUST revert at the end with the simulation results.Gives the preCrime implementation the ability to mock sending packets to the lzReceive function, WITHOUT actually executing them.", + "params": { + "_packets": "An array of InboundPacket objects representing received packets to be delivered." + } + }, + "lzReceiveSimulate((uint32,bytes32,uint64),bytes32,bytes,address,bytes)": { + "details": "Is effectively an internal function because msg.sender must be address(this). Allows resetting the call stack for 'internal' calls.", + "params": { + "_executor": "The executor address for the packet.", + "_extraData": "Additional data for the packet.", + "_guid": "The unique identifier of the packet.", + "_message": "The message payload of the packet.", + "_origin": "The origin information containing the source endpoint and sender address. - srcEid: The source chain endpoint ID. - sender: The sender address on the src chain. - nonce: The nonce of the message." + } + }, + "mint(address,uint256)": { + "details": "Deliberately unguarded: the demo mints to itself before each send, and a testnet OFT with no way to obtain tokens cannot be used to exercise the DVN at all. Do NOT ship this to a network where the token has value. `virtual` because MyOFTMock declares the same function for the hardhat tests." + }, + "name()": { + "details": "Returns the name of the token." + }, + "nextNonce(uint32,bytes32)": { + "details": "_srcEid The source endpoint ID._sender The sender address.The path nonce starts from 1. If 0 is returned it means that there is NO nonce ordered enforcement.Is required by the off-chain executor to determine the OApp expects msg execution is ordered.This is also enforced by the OApp.By default this is NOT enabled. ie. nextNonce is hardcoded to return 0.", + "returns": { + "nonce": "The next nonce." + } + }, + "oApp()": { + "details": "Retrieves the address of the OApp contract.The simulator contract is the base contract for the OApp by default.If the simulator is a separate contract, override this function.", + "returns": { + "_0": "The address of the OApp contract." + } + }, + "oAppVersion()": { + "returns": { + "receiverVersion": "The version of the OAppReceiver.sol implementation.", + "senderVersion": "The version of the OAppSender.sol implementation." + } + }, + "oftVersion()": { + "details": "interfaceId: This specific interface ID is '0x02e49c2c'.version: Indicates a cross-chain compatible msg encoding with other OFTs.If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented. ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)", + "returns": { + "interfaceId": "The interface ID.", + "version": "The version." + } + }, + "owner()": { + "details": "Returns the address of the current owner." + }, + "quoteOFT((uint32,bytes32,uint256,uint256,bytes,bytes,bytes))": { + "params": { + "_sendParam": "The parameters for the send operation." + }, + "returns": { + "oftFeeDetails": "The details of OFT fees.", + "oftLimit": "The OFT limit information.", + "oftReceipt": "The OFT receipt information." + } + }, + "quoteSend((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),bool)": { + "details": "MessagingFee: LayerZero msg fee - nativeFee: The native fee. - lzTokenFee: The lzToken fee.", + "params": { + "_payInLzToken": "Flag indicating whether the caller is paying in the LZ token.", + "_sendParam": "The parameters for the send() operation." + }, + "returns": { + "msgFee": "The calculated LayerZero messaging fee from the send() operation." + } + }, + "renounceOwnership()": { + "details": "Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner." + }, + "send((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),(uint256,uint256),address)": { + "details": "Executes the send operation.MessagingReceipt: LayerZero msg receipt - guid: The unique identifier for the sent message. - nonce: The nonce of the sent message. - fee: The LayerZero fee incurred for the message.", + "params": { + "_fee": "The calculated fee for the send() operation. - nativeFee: The native fee. - lzTokenFee: The lzToken fee.", + "_refundAddress": "The address to receive any excess funds.", + "_sendParam": "The parameters for the send operation." + }, + "returns": { + "msgReceipt": "The receipt for the send operation.", + "oftReceipt": "The OFT receipt information." + } + }, + "setDelegate(address)": { + "details": "Only the owner/admin of the OApp can call this function.Provides the ability for a delegate to set configs, on behalf of the OApp, directly on the Endpoint contract.", + "params": { + "_delegate": "The address of the delegate to be set." + } + }, + "setEnforcedOptions((uint32,uint16,bytes)[])": { + "details": "Sets the enforced options for specific endpoint and message type combinations.Only the owner/admin of the OApp can call this function.Provides a way for the OApp to enforce things like paying for PreCrime, AND/OR minimum dst lzReceive gas amounts etc.These enforced options can vary as the potential options/execution on the remote may differ as per the msgType. eg. Amount of lzReceive() gas necessary to deliver a lzCompose() message adds overhead you dont want to pay if you are only making a standard LayerZero message ie. lzReceive() WITHOUT sendCompose().", + "params": { + "_enforcedOptions": "An array of EnforcedOptionParam structures specifying enforced options." + } + }, + "setMsgInspector(address)": { + "details": "Sets the message inspector address for the OFT.This is an optional contract that can be used to inspect both 'message' and 'options'.Set it to address(0) to disable it, or set it to a contract address to enable it.", + "params": { + "_msgInspector": "The address of the message inspector." + } + }, + "setPeer(uint32,bytes32)": { + "details": "Only the owner/admin of the OApp can call this function.Indicates that the peer is trusted to send LayerZero messages to this OApp.Set this to bytes32(0) to remove the peer address.Peer is a bytes32 to accommodate non-evm chains.", + "params": { + "_eid": "The endpoint ID.", + "_peer": "The address of the peer to be associated with the corresponding endpoint." + } + }, + "setPreCrime(address)": { + "details": "Sets the preCrime contract address.", + "params": { + "_preCrime": "The address of the preCrime contract." + } + }, + "sharedDecimals()": { + "details": "Retrieves the shared decimals of the OFT.Sets an implicit cap on the amount of tokens, over uint64.max() will need some sort of outbound cap / totalSupply cap Lowest common decimal denominator between chains. Defaults to 6 decimal places to provide up to 18,446,744,073,709.551615 units (max uint64). For tokens exceeding this totalSupply(), they will need to override the sharedDecimals function with something smaller. ie. 4 sharedDecimals would be 1,844,674,407,370,955.1615", + "returns": { + "_0": "The shared decimals of the OFT." + } + }, + "symbol()": { + "details": "Returns the symbol of the token, usually a shorter version of the name." + }, + "token()": { + "details": "Retrieves the address of the underlying ERC20 implementation.In the case of OFT, address(this) and erc20 are the same contract.", + "returns": { + "_0": "The address of the OFT token." + } + }, + "totalSupply()": { + "details": "Returns the value of tokens in existence." + }, + "transfer(address,uint256)": { + "details": "See {IERC20-transfer}. Requirements: - `to` cannot be the zero address. - the caller must have a balance of at least `value`." + }, + "transferFrom(address,address,uint256)": { + "details": "See {IERC20-transferFrom}. Skips emitting an {Approval} event indicating an allowance update. This is not required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve]. NOTE: Does not update the allowance if the current allowance is the maximum `uint256`. Requirements: - `from` and `to` cannot be the zero address. - `from` must have a balance of at least `value`. - the caller must have allowance for ``from``'s tokens of at least `value`." + }, + "transferOwnership(address)": { + "details": "Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner." + } + }, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "allowInitializePath((uint32,bytes32,uint64))": { + "notice": "Checks if the path initialization is allowed based on the provided origin." + }, + "approvalRequired()": { + "notice": "Indicates whether the OFT contract requires approval of the 'token()' to send." + }, + "combineOptions(uint32,uint16,bytes)": { + "notice": "Combines options for a given endpoint and message type." + }, + "endpoint()": { + "notice": "Retrieves the LayerZero endpoint associated with the OApp." + }, + "isComposeMsgSender((uint32,bytes32,uint64),bytes,address)": { + "notice": "Indicates whether an address is an approved composeMsg sender to the Endpoint." + }, + "mint(address,uint256)": { + "notice": "Open mint, TESTNET ONLY — same as ToyOFT, so the demo tasks work against either." + }, + "nextNonce(uint32,bytes32)": { + "notice": "Retrieves the next nonce for a given source endpoint and sender address." + }, + "oAppVersion()": { + "notice": "Retrieves the OApp version information." + }, + "oftVersion()": { + "notice": "Retrieves interfaceID and the version of the OFT." + }, + "peers(uint32)": { + "notice": "Retrieves the peer (OApp) associated with a corresponding endpoint." + }, + "quoteOFT((uint32,bytes32,uint256,uint256,bytes,bytes,bytes))": { + "notice": "Provides the fee breakdown and settings data for an OFT. Unused in the default implementation." + }, + "quoteSend((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),bool)": { + "notice": "Provides a quote for the send() operation." + }, + "setDelegate(address)": { + "notice": "Sets the delegate address for the OApp." + }, + "setPeer(uint32,bytes32)": { + "notice": "Sets the peer address (OApp instance) for a corresponding endpoint." + } + }, + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 3861, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "t_address" + }, + { + "astId": 1390, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "peers", + "offset": 0, + "slot": "1", + "type": "t_mapping(t_uint32,t_bytes32)" + }, + { + "astId": 2166, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "preCrime", + "offset": 0, + "slot": "2", + "type": "t_address" + }, + { + "astId": 2006, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "enforcedOptions", + "offset": 0, + "slot": "3", + "type": "t_mapping(t_uint32,t_mapping(t_uint16,t_bytes_storage))" + }, + { + "astId": 2786, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "msgInspector", + "offset": 0, + "slot": "4", + "type": "t_address" + }, + { + "astId": 4250, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "_balances", + "offset": 0, + "slot": "5", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 4256, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "_allowances", + "offset": 0, + "slot": "6", + "type": "t_mapping(t_address,t_mapping(t_address,t_uint256))" + }, + { + "astId": 4258, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "_totalSupply", + "offset": 0, + "slot": "7", + "type": "t_uint256" + }, + { + "astId": 4260, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "_name", + "offset": 0, + "slot": "8", + "type": "t_string_storage" + }, + { + "astId": 4262, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "_symbol", + "offset": 0, + "slot": "9", + "type": "t_string_storage" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_bytes32": { + "encoding": "inplace", + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_bytes_storage": { + "encoding": "bytes", + "label": "bytes", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32", + "value": "t_mapping(t_address,t_uint256)" + }, + "t_mapping(t_address,t_uint256)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_mapping(t_uint16,t_bytes_storage)": { + "encoding": "mapping", + "key": "t_uint16", + "label": "mapping(uint16 => bytes)", + "numberOfBytes": "32", + "value": "t_bytes_storage" + }, + "t_mapping(t_uint32,t_bytes32)": { + "encoding": "mapping", + "key": "t_uint32", + "label": "mapping(uint32 => bytes32)", + "numberOfBytes": "32", + "value": "t_bytes32" + }, + "t_mapping(t_uint32,t_mapping(t_uint16,t_bytes_storage))": { + "encoding": "mapping", + "key": "t_uint32", + "label": "mapping(uint32 => mapping(uint16 => bytes))", + "numberOfBytes": "32", + "value": "t_mapping(t_uint16,t_bytes_storage)" + }, + "t_string_storage": { + "encoding": "bytes", + "label": "string", + "numberOfBytes": "32" + }, + "t_uint16": { + "encoding": "inplace", + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint32": { + "encoding": "inplace", + "label": "uint32", + "numberOfBytes": "4" + } + } + } +} \ No newline at end of file diff --git a/deployments/optimism-sepolia/MyOFT.json b/deployments/optimism-sepolia/MyOFT.json new file mode 100644 index 0000000..fcd1c4d --- /dev/null +++ b/deployments/optimism-sepolia/MyOFT.json @@ -0,0 +1,2191 @@ +{ + "address": "0x5237Ca5731f00741E10E5dB0cedA0796e21f08a6", + "abi": [ + { + "inputs": [ + { + "internalType": "string", + "name": "_name", + "type": "string" + }, + { + "internalType": "string", + "name": "_symbol", + "type": "string" + }, + { + "internalType": "address", + "name": "_lzEndpoint", + "type": "address" + }, + { + "internalType": "address", + "name": "_delegate", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountSD", + "type": "uint256" + } + ], + "name": "AmountSDOverflowed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "allowance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientAllowance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "ERC20InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC20InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "ERC20InvalidSender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "ERC20InvalidSpender", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidDelegate", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidEndpointCall", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidLocalDecimals", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "options", + "type": "bytes" + } + ], + "name": "InvalidOptions", + "type": "error" + }, + { + "inputs": [], + "name": "LzTokenUnavailable", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "eid", + "type": "uint32" + } + ], + "name": "NoPeer", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "msgValue", + "type": "uint256" + } + ], + "name": "NotEnoughNative", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "OnlyEndpoint", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "eid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + } + ], + "name": "OnlyPeer", + "type": "error" + }, + { + "inputs": [], + "name": "OnlySelf", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "result", + "type": "bytes" + } + ], + "name": "SimulationResult", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountLD", + "type": "uint256" + } + ], + "name": "SlippageExceeded", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "eid", + "type": "uint32" + }, + { + "internalType": "uint16", + "name": "msgType", + "type": "uint16" + }, + { + "internalType": "bytes", + "name": "options", + "type": "bytes" + } + ], + "indexed": false, + "internalType": "struct EnforcedOptionParam[]", + "name": "_enforcedOptions", + "type": "tuple[]" + } + ], + "name": "EnforcedOptionSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "inspector", + "type": "address" + } + ], + "name": "MsgInspectorSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "guid", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "srcEid", + "type": "uint32" + }, + { + "indexed": true, + "internalType": "address", + "name": "toAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountReceivedLD", + "type": "uint256" + } + ], + "name": "OFTReceived", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "guid", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "dstEid", + "type": "uint32" + }, + { + "indexed": true, + "internalType": "address", + "name": "fromAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountSentLD", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountReceivedLD", + "type": "uint256" + } + ], + "name": "OFTSent", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint32", + "name": "eid", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "peer", + "type": "bytes32" + } + ], + "name": "PeerSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "preCrimeAddress", + "type": "address" + } + ], + "name": "PreCrimeSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "SEND", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SEND_AND_CALL", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "srcEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "internalType": "struct Origin", + "name": "origin", + "type": "tuple" + } + ], + "name": "allowInitializePath", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "approvalRequired", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_eid", + "type": "uint32" + }, + { + "internalType": "uint16", + "name": "_msgType", + "type": "uint16" + }, + { + "internalType": "bytes", + "name": "_extraOptions", + "type": "bytes" + } + ], + "name": "combineOptions", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimalConversionRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "endpoint", + "outputs": [ + { + "internalType": "contract ILayerZeroEndpointV2", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "eid", + "type": "uint32" + }, + { + "internalType": "uint16", + "name": "msgType", + "type": "uint16" + } + ], + "name": "enforcedOptions", + "outputs": [ + { + "internalType": "bytes", + "name": "enforcedOption", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "srcEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "internalType": "struct Origin", + "name": "", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_sender", + "type": "address" + } + ], + "name": "isComposeMsgSender", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_eid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "_peer", + "type": "bytes32" + } + ], + "name": "isPeer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "srcEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "internalType": "struct Origin", + "name": "_origin", + "type": "tuple" + }, + { + "internalType": "bytes32", + "name": "_guid", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "_message", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_executor", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_extraData", + "type": "bytes" + } + ], + "name": "lzReceive", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "uint32", + "name": "srcEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "internalType": "struct Origin", + "name": "origin", + "type": "tuple" + }, + { + "internalType": "uint32", + "name": "dstEid", + "type": "uint32" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "guid", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "address", + "name": "executor", + "type": "address" + }, + { + "internalType": "bytes", + "name": "message", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "internalType": "struct InboundPacket[]", + "name": "_packets", + "type": "tuple[]" + } + ], + "name": "lzReceiveAndRevert", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "srcEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "internalType": "struct Origin", + "name": "_origin", + "type": "tuple" + }, + { + "internalType": "bytes32", + "name": "_guid", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "_message", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_executor", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_extraData", + "type": "bytes" + } + ], + "name": "lzReceiveSimulate", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "msgInspector", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "nextNonce", + "outputs": [ + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oApp", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oAppVersion", + "outputs": [ + { + "internalType": "uint64", + "name": "senderVersion", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "receiverVersion", + "type": "uint64" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "oftVersion", + "outputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + }, + { + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "eid", + "type": "uint32" + } + ], + "name": "peers", + "outputs": [ + { + "internalType": "bytes32", + "name": "peer", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "preCrime", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "dstEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "to", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amountLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountLD", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "extraOptions", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "composeMsg", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "oftCmd", + "type": "bytes" + } + ], + "internalType": "struct SendParam", + "name": "_sendParam", + "type": "tuple" + } + ], + "name": "quoteOFT", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "minAmountLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxAmountLD", + "type": "uint256" + } + ], + "internalType": "struct OFTLimit", + "name": "oftLimit", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "int256", + "name": "feeAmountLD", + "type": "int256" + }, + { + "internalType": "string", + "name": "description", + "type": "string" + } + ], + "internalType": "struct OFTFeeDetail[]", + "name": "oftFeeDetails", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "amountSentLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountReceivedLD", + "type": "uint256" + } + ], + "internalType": "struct OFTReceipt", + "name": "oftReceipt", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "dstEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "to", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amountLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountLD", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "extraOptions", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "composeMsg", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "oftCmd", + "type": "bytes" + } + ], + "internalType": "struct SendParam", + "name": "_sendParam", + "type": "tuple" + }, + { + "internalType": "bool", + "name": "_payInLzToken", + "type": "bool" + } + ], + "name": "quoteSend", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "nativeFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lzTokenFee", + "type": "uint256" + } + ], + "internalType": "struct MessagingFee", + "name": "msgFee", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "dstEid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "to", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amountLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountLD", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "extraOptions", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "composeMsg", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "oftCmd", + "type": "bytes" + } + ], + "internalType": "struct SendParam", + "name": "_sendParam", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "nativeFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lzTokenFee", + "type": "uint256" + } + ], + "internalType": "struct MessagingFee", + "name": "_fee", + "type": "tuple" + }, + { + "internalType": "address", + "name": "_refundAddress", + "type": "address" + } + ], + "name": "send", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "guid", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "nativeFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lzTokenFee", + "type": "uint256" + } + ], + "internalType": "struct MessagingFee", + "name": "fee", + "type": "tuple" + } + ], + "internalType": "struct MessagingReceipt", + "name": "msgReceipt", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "amountSentLD", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountReceivedLD", + "type": "uint256" + } + ], + "internalType": "struct OFTReceipt", + "name": "oftReceipt", + "type": "tuple" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_delegate", + "type": "address" + } + ], + "name": "setDelegate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "eid", + "type": "uint32" + }, + { + "internalType": "uint16", + "name": "msgType", + "type": "uint16" + }, + { + "internalType": "bytes", + "name": "options", + "type": "bytes" + } + ], + "internalType": "struct EnforcedOptionParam[]", + "name": "_enforcedOptions", + "type": "tuple[]" + } + ], + "name": "setEnforcedOptions", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_msgInspector", + "type": "address" + } + ], + "name": "setMsgInspector", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_eid", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "_peer", + "type": "bytes32" + } + ], + "name": "setPeer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_preCrime", + "type": "address" + } + ], + "name": "setPreCrime", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "sharedDecimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "transactionHash": "0x30e23983365a1f9cc3de1095542793ea9c9b76cfb437b9292ade42ad1bfbd8d2", + "receipt": { + "to": null, + "from": "0x8583894d0e57e42abb83039537f314490038efa0", + "contractAddress": "0x5237Ca5731f00741E10E5dB0cedA0796e21f08a6", + "transactionIndex": 1, + "gasUsed": "2887200", + "logsBloom": "0x00000000000000000000000000000000000000000000000000800000000001000000000000000000220000000000000000000002000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000100020000000200000000000800000000000000000000000008000000400000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x2b789bd08dd0295c173017a3a9f9b5505de74dc57eeebfa5d3718cf6c0218123", + "transactionHash": "0x30e23983365a1f9cc3de1095542793ea9c9b76cfb437b9292ade42ad1bfbd8d2", + "logs": [ + { + "transactionIndex": 1, + "blockNumber": 46858134, + "transactionHash": "0x30e23983365a1f9cc3de1095542793ea9c9b76cfb437b9292ade42ad1bfbd8d2", + "address": "0x5237Ca5731f00741E10E5dB0cedA0796e21f08a6", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000008583894d0e57e42abb83039537f314490038efa0" + ], + "data": "0x", + "logIndex": 0, + "blockHash": "0x2b789bd08dd0295c173017a3a9f9b5505de74dc57eeebfa5d3718cf6c0218123" + }, + { + "transactionIndex": 1, + "blockNumber": 46858134, + "transactionHash": "0x30e23983365a1f9cc3de1095542793ea9c9b76cfb437b9292ade42ad1bfbd8d2", + "address": "0x6EDCE65403992e310A62460808c4b910D972f10f", + "topics": [ + "0x6ee10e9ed4d6ce9742703a498707862f4b00f1396a87195eb93267b3d7983981" + ], + "data": "0x0000000000000000000000005237ca5731f00741e10e5db0ceda0796e21f08a60000000000000000000000008583894d0e57e42abb83039537f314490038efa0", + "logIndex": 1, + "blockHash": "0x2b789bd08dd0295c173017a3a9f9b5505de74dc57eeebfa5d3718cf6c0218123" + } + ], + "blockNumber": 46858134, + "cumulativeGasUsed": "2933430", + "status": 1, + "byzantium": true + }, + "args": [ + "testUSDT", + "testUSDT", + "0x6EDCE65403992e310A62460808c4b910D972f10f", + "0x8583894d0e57e42abb83039537f314490038efa0" + ], + "numDeployments": 2, + "solcInputHash": "8088d7b064191499b181ffda0ed40a97", + "metadata": "{\"compiler\":{\"version\":\"0.8.22+commit.4fc1097e\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_name\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"_symbol\",\"type\":\"string\"},{\"internalType\":\"address\",\"name\":\"_lzEndpoint\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_delegate\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amountSD\",\"type\":\"uint256\"}],\"name\":\"AmountSDOverflowed\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"allowance\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"needed\",\"type\":\"uint256\"}],\"name\":\"ERC20InsufficientAllowance\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"needed\",\"type\":\"uint256\"}],\"name\":\"ERC20InsufficientBalance\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"approver\",\"type\":\"address\"}],\"name\":\"ERC20InvalidApprover\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"}],\"name\":\"ERC20InvalidReceiver\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"ERC20InvalidSender\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"ERC20InvalidSpender\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidDelegate\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidEndpointCall\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidLocalDecimals\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"options\",\"type\":\"bytes\"}],\"name\":\"InvalidOptions\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"LzTokenUnavailable\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"}],\"name\":\"NoPeer\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"msgValue\",\"type\":\"uint256\"}],\"name\":\"NotEnoughNative\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"OnlyEndpoint\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"}],\"name\":\"OnlyPeer\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlySelf\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"OwnableInvalidOwner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"OwnableUnauthorizedAccount\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"}],\"name\":\"SafeERC20FailedOperation\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"result\",\"type\":\"bytes\"}],\"name\":\"SimulationResult\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amountLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minAmountLD\",\"type\":\"uint256\"}],\"name\":\"SlippageExceeded\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"msgType\",\"type\":\"uint16\"},{\"internalType\":\"bytes\",\"name\":\"options\",\"type\":\"bytes\"}],\"indexed\":false,\"internalType\":\"struct EnforcedOptionParam[]\",\"name\":\"_enforcedOptions\",\"type\":\"tuple[]\"}],\"name\":\"EnforcedOptionSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"inspector\",\"type\":\"address\"}],\"name\":\"MsgInspectorSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"guid\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"srcEid\",\"type\":\"uint32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"toAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amountReceivedLD\",\"type\":\"uint256\"}],\"name\":\"OFTReceived\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"guid\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"fromAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amountSentLD\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amountReceivedLD\",\"type\":\"uint256\"}],\"name\":\"OFTSent\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"peer\",\"type\":\"bytes32\"}],\"name\":\"PeerSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"preCrimeAddress\",\"type\":\"address\"}],\"name\":\"PreCrimeSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"SEND\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"\",\"type\":\"uint16\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"SEND_AND_CALL\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"\",\"type\":\"uint16\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"srcEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"struct Origin\",\"name\":\"origin\",\"type\":\"tuple\"}],\"name\":\"allowInitializePath\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"approvalRequired\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"_eid\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"_msgType\",\"type\":\"uint16\"},{\"internalType\":\"bytes\",\"name\":\"_extraOptions\",\"type\":\"bytes\"}],\"name\":\"combineOptions\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimalConversionRate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"endpoint\",\"outputs\":[{\"internalType\":\"contract ILayerZeroEndpointV2\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"msgType\",\"type\":\"uint16\"}],\"name\":\"enforcedOptions\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"enforcedOption\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"srcEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"struct Origin\",\"name\":\"\",\"type\":\"tuple\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"_sender\",\"type\":\"address\"}],\"name\":\"isComposeMsgSender\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"_eid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"_peer\",\"type\":\"bytes32\"}],\"name\":\"isPeer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"srcEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"struct Origin\",\"name\":\"_origin\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"_guid\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"_message\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"_executor\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_extraData\",\"type\":\"bytes\"}],\"name\":\"lzReceive\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"srcEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"struct Origin\",\"name\":\"origin\",\"type\":\"tuple\"},{\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"guid\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"executor\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"message\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"extraData\",\"type\":\"bytes\"}],\"internalType\":\"struct InboundPacket[]\",\"name\":\"_packets\",\"type\":\"tuple[]\"}],\"name\":\"lzReceiveAndRevert\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"srcEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"struct Origin\",\"name\":\"_origin\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"_guid\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"_message\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"_executor\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_extraData\",\"type\":\"bytes\"}],\"name\":\"lzReceiveSimulate\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"mint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"msgInspector\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"nextNonce\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"oApp\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"oAppVersion\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"senderVersion\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"receiverVersion\",\"type\":\"uint64\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"oftVersion\",\"outputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"},{\"internalType\":\"uint64\",\"name\":\"version\",\"type\":\"uint64\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"}],\"name\":\"peers\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"peer\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"preCrime\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"to\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"amountLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minAmountLD\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"extraOptions\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"composeMsg\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"oftCmd\",\"type\":\"bytes\"}],\"internalType\":\"struct SendParam\",\"name\":\"_sendParam\",\"type\":\"tuple\"}],\"name\":\"quoteOFT\",\"outputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"minAmountLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxAmountLD\",\"type\":\"uint256\"}],\"internalType\":\"struct OFTLimit\",\"name\":\"oftLimit\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"int256\",\"name\":\"feeAmountLD\",\"type\":\"int256\"},{\"internalType\":\"string\",\"name\":\"description\",\"type\":\"string\"}],\"internalType\":\"struct OFTFeeDetail[]\",\"name\":\"oftFeeDetails\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"amountSentLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountReceivedLD\",\"type\":\"uint256\"}],\"internalType\":\"struct OFTReceipt\",\"name\":\"oftReceipt\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"to\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"amountLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minAmountLD\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"extraOptions\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"composeMsg\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"oftCmd\",\"type\":\"bytes\"}],\"internalType\":\"struct SendParam\",\"name\":\"_sendParam\",\"type\":\"tuple\"},{\"internalType\":\"bool\",\"name\":\"_payInLzToken\",\"type\":\"bool\"}],\"name\":\"quoteSend\",\"outputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"nativeFee\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"lzTokenFee\",\"type\":\"uint256\"}],\"internalType\":\"struct MessagingFee\",\"name\":\"msgFee\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"dstEid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"to\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"amountLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minAmountLD\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"extraOptions\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"composeMsg\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"oftCmd\",\"type\":\"bytes\"}],\"internalType\":\"struct SendParam\",\"name\":\"_sendParam\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"nativeFee\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"lzTokenFee\",\"type\":\"uint256\"}],\"internalType\":\"struct MessagingFee\",\"name\":\"_fee\",\"type\":\"tuple\"},{\"internalType\":\"address\",\"name\":\"_refundAddress\",\"type\":\"address\"}],\"name\":\"send\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"guid\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"nativeFee\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"lzTokenFee\",\"type\":\"uint256\"}],\"internalType\":\"struct MessagingFee\",\"name\":\"fee\",\"type\":\"tuple\"}],\"internalType\":\"struct MessagingReceipt\",\"name\":\"msgReceipt\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"amountSentLD\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountReceivedLD\",\"type\":\"uint256\"}],\"internalType\":\"struct OFTReceipt\",\"name\":\"oftReceipt\",\"type\":\"tuple\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_delegate\",\"type\":\"address\"}],\"name\":\"setDelegate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"eid\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"msgType\",\"type\":\"uint16\"},{\"internalType\":\"bytes\",\"name\":\"options\",\"type\":\"bytes\"}],\"internalType\":\"struct EnforcedOptionParam[]\",\"name\":\"_enforcedOptions\",\"type\":\"tuple[]\"}],\"name\":\"setEnforcedOptions\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_msgInspector\",\"type\":\"address\"}],\"name\":\"setMsgInspector\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"_eid\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"_peer\",\"type\":\"bytes32\"}],\"name\":\"setPeer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_preCrime\",\"type\":\"address\"}],\"name\":\"setPreCrime\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"sharedDecimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"errors\":{\"ERC20InsufficientAllowance(address,uint256,uint256)\":[{\"details\":\"Indicates a failure with the `spender`\\u2019s `allowance`. Used in transfers.\",\"params\":{\"allowance\":\"Amount of tokens a `spender` is allowed to operate with.\",\"needed\":\"Minimum amount required to perform a transfer.\",\"spender\":\"Address that may be allowed to operate on tokens without being their owner.\"}}],\"ERC20InsufficientBalance(address,uint256,uint256)\":[{\"details\":\"Indicates an error related to the current `balance` of a `sender`. Used in transfers.\",\"params\":{\"balance\":\"Current balance for the interacting account.\",\"needed\":\"Minimum amount required to perform a transfer.\",\"sender\":\"Address whose tokens are being transferred.\"}}],\"ERC20InvalidApprover(address)\":[{\"details\":\"Indicates a failure with the `approver` of a token to be approved. Used in approvals.\",\"params\":{\"approver\":\"Address initiating an approval operation.\"}}],\"ERC20InvalidReceiver(address)\":[{\"details\":\"Indicates a failure with the token `receiver`. Used in transfers.\",\"params\":{\"receiver\":\"Address to which tokens are being transferred.\"}}],\"ERC20InvalidSender(address)\":[{\"details\":\"Indicates a failure with the token `sender`. Used in transfers.\",\"params\":{\"sender\":\"Address whose tokens are being transferred.\"}}],\"ERC20InvalidSpender(address)\":[{\"details\":\"Indicates a failure with the `spender` to be approved. Used in approvals.\",\"params\":{\"spender\":\"Address that may be allowed to operate on tokens without being their owner.\"}}],\"OwnableInvalidOwner(address)\":[{\"details\":\"The owner is not a valid owner account. (eg. `address(0)`)\"}],\"OwnableUnauthorizedAccount(address)\":[{\"details\":\"The caller account is not authorized to perform an operation.\"}],\"SafeERC20FailedOperation(address)\":[{\"details\":\"An operation with an ERC-20 token failed.\"}]},\"events\":{\"Approval(address,address,uint256)\":{\"details\":\"Emitted when the allowance of a `spender` for an `owner` is set by a call to {approve}. `value` is the new allowance.\"},\"PreCrimeSet(address)\":{\"details\":\"Emitted when the preCrime contract address is set.\",\"params\":{\"preCrimeAddress\":\"The address of the preCrime contract.\"}},\"Transfer(address,address,uint256)\":{\"details\":\"Emitted when `value` tokens are moved from one account (`from`) to another (`to`). Note that `value` may be zero.\"}},\"kind\":\"dev\",\"methods\":{\"allowInitializePath((uint32,bytes32,uint64))\":{\"details\":\"This indicates to the endpoint that the OApp has enabled msgs for this particular path to be received.This defaults to assuming if a peer has been set, its initialized. Can be overridden by the OApp if there is other logic to determine this.\",\"params\":{\"origin\":\"The origin information containing the source endpoint and sender address.\"},\"returns\":{\"_0\":\"Whether the path has been initialized.\"}},\"allowance(address,address)\":{\"details\":\"Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` through {transferFrom}. This is zero by default. This value changes when {approve} or {transferFrom} are called.\"},\"approvalRequired()\":{\"details\":\"In the case of OFT where the contract IS the token, approval is NOT required.\",\"returns\":{\"_0\":\"requiresApproval Needs approval of the underlying token implementation.\"}},\"approve(address,uint256)\":{\"details\":\"See {IERC20-approve}. NOTE: If `value` is the maximum `uint256`, the allowance is not updated on `transferFrom`. This is semantically equivalent to an infinite approval. Requirements: - `spender` cannot be the zero address.\"},\"balanceOf(address)\":{\"details\":\"Returns the value of tokens owned by `account`.\"},\"combineOptions(uint32,uint16,bytes)\":{\"details\":\"If there is an enforced lzReceive option: - {gasLimit: 200k, msg.value: 1 ether} AND a caller supplies a lzReceive option: {gasLimit: 100k, msg.value: 0.5 ether} - The resulting options will be {gasLimit: 300k, msg.value: 1.5 ether} when the message is executed on the remote lzReceive() function.This presence of duplicated options is handled off-chain in the verifier/executor.\",\"params\":{\"_eid\":\"The endpoint ID.\",\"_extraOptions\":\"Additional options passed by the caller.\",\"_msgType\":\"The OAPP message type.\"},\"returns\":{\"_0\":\"options The combination of caller specified options AND enforced options.\"}},\"decimals()\":{\"details\":\"Returns the number of decimals used to get its user representation. For example, if `decimals` equals `2`, a balance of `505` tokens should be displayed to a user as `5.05` (`505 / 10 ** 2`). Tokens usually opt for a value of 18, imitating the relationship between Ether and Wei. This is the default value returned by this function, unless it's overridden. NOTE: This information is only used for _display_ purposes: it in no way affects any of the arithmetic of the contract, including {IERC20-balanceOf} and {IERC20-transfer}.\"},\"isComposeMsgSender((uint32,bytes32,uint64),bytes,address)\":{\"details\":\"_origin The origin information containing the source endpoint and sender address. - srcEid: The source chain endpoint ID. - sender: The sender address on the src chain. - nonce: The nonce of the message._message The lzReceive payload.Applications can optionally choose to implement separate composeMsg senders that are NOT the bridging layer.The default sender IS the OAppReceiver implementer.\",\"params\":{\"_sender\":\"The sender address.\"},\"returns\":{\"_0\":\"isSender Is a valid sender.\"}},\"isPeer(uint32,bytes32)\":{\"details\":\"Check if the peer is considered 'trusted' by the OApp.Enables OAppPreCrimeSimulator to check whether a potential Inbound Packet is from a trusted source.\",\"params\":{\"_eid\":\"The endpoint ID to check.\",\"_peer\":\"The peer to check.\"},\"returns\":{\"_0\":\"Whether the peer passed is considered 'trusted' by the OApp.\"}},\"lzReceive((uint32,bytes32,uint64),bytes32,bytes,address,bytes)\":{\"details\":\"Entry point for receiving messages or packets from the endpoint.Entry point for receiving msg/packet from the LayerZero endpoint.\",\"params\":{\"_executor\":\"The address of the executor for the received message.\",\"_extraData\":\"Additional arbitrary data provided by the corresponding executor.\",\"_guid\":\"The unique identifier for the received LayerZero message.\",\"_message\":\"The payload of the received message.\",\"_origin\":\"The origin information containing the source endpoint and sender address. - srcEid: The source chain endpoint ID. - sender: The sender address on the src chain. - nonce: The nonce of the message.\"}},\"lzReceiveAndRevert(((uint32,bytes32,uint64),uint32,address,bytes32,uint256,address,bytes,bytes)[])\":{\"details\":\"Interface for pre-crime simulations. Always reverts at the end with the simulation results.WARNING: MUST revert at the end with the simulation results.Gives the preCrime implementation the ability to mock sending packets to the lzReceive function, WITHOUT actually executing them.\",\"params\":{\"_packets\":\"An array of InboundPacket objects representing received packets to be delivered.\"}},\"lzReceiveSimulate((uint32,bytes32,uint64),bytes32,bytes,address,bytes)\":{\"details\":\"Is effectively an internal function because msg.sender must be address(this). Allows resetting the call stack for 'internal' calls.\",\"params\":{\"_executor\":\"The executor address for the packet.\",\"_extraData\":\"Additional data for the packet.\",\"_guid\":\"The unique identifier of the packet.\",\"_message\":\"The message payload of the packet.\",\"_origin\":\"The origin information containing the source endpoint and sender address. - srcEid: The source chain endpoint ID. - sender: The sender address on the src chain. - nonce: The nonce of the message.\"}},\"mint(address,uint256)\":{\"details\":\"Deliberately unguarded: the demo mints to itself before each send, and a testnet OFT with no way to obtain tokens cannot be used to exercise the DVN at all. Do NOT ship this to a network where the token has value. `virtual` because MyOFTMock declares the same function for the hardhat tests.\"},\"name()\":{\"details\":\"Returns the name of the token.\"},\"nextNonce(uint32,bytes32)\":{\"details\":\"_srcEid The source endpoint ID._sender The sender address.The path nonce starts from 1. If 0 is returned it means that there is NO nonce ordered enforcement.Is required by the off-chain executor to determine the OApp expects msg execution is ordered.This is also enforced by the OApp.By default this is NOT enabled. ie. nextNonce is hardcoded to return 0.\",\"returns\":{\"nonce\":\"The next nonce.\"}},\"oApp()\":{\"details\":\"Retrieves the address of the OApp contract.The simulator contract is the base contract for the OApp by default.If the simulator is a separate contract, override this function.\",\"returns\":{\"_0\":\"The address of the OApp contract.\"}},\"oAppVersion()\":{\"returns\":{\"receiverVersion\":\"The version of the OAppReceiver.sol implementation.\",\"senderVersion\":\"The version of the OAppSender.sol implementation.\"}},\"oftVersion()\":{\"details\":\"interfaceId: This specific interface ID is '0x02e49c2c'.version: Indicates a cross-chain compatible msg encoding with other OFTs.If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented. ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)\",\"returns\":{\"interfaceId\":\"The interface ID.\",\"version\":\"The version.\"}},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"quoteOFT((uint32,bytes32,uint256,uint256,bytes,bytes,bytes))\":{\"params\":{\"_sendParam\":\"The parameters for the send operation.\"},\"returns\":{\"oftFeeDetails\":\"The details of OFT fees.\",\"oftLimit\":\"The OFT limit information.\",\"oftReceipt\":\"The OFT receipt information.\"}},\"quoteSend((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),bool)\":{\"details\":\"MessagingFee: LayerZero msg fee - nativeFee: The native fee. - lzTokenFee: The lzToken fee.\",\"params\":{\"_payInLzToken\":\"Flag indicating whether the caller is paying in the LZ token.\",\"_sendParam\":\"The parameters for the send() operation.\"},\"returns\":{\"msgFee\":\"The calculated LayerZero messaging fee from the send() operation.\"}},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner.\"},\"send((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),(uint256,uint256),address)\":{\"details\":\"Executes the send operation.MessagingReceipt: LayerZero msg receipt - guid: The unique identifier for the sent message. - nonce: The nonce of the sent message. - fee: The LayerZero fee incurred for the message.\",\"params\":{\"_fee\":\"The calculated fee for the send() operation. - nativeFee: The native fee. - lzTokenFee: The lzToken fee.\",\"_refundAddress\":\"The address to receive any excess funds.\",\"_sendParam\":\"The parameters for the send operation.\"},\"returns\":{\"msgReceipt\":\"The receipt for the send operation.\",\"oftReceipt\":\"The OFT receipt information.\"}},\"setDelegate(address)\":{\"details\":\"Only the owner/admin of the OApp can call this function.Provides the ability for a delegate to set configs, on behalf of the OApp, directly on the Endpoint contract.\",\"params\":{\"_delegate\":\"The address of the delegate to be set.\"}},\"setEnforcedOptions((uint32,uint16,bytes)[])\":{\"details\":\"Sets the enforced options for specific endpoint and message type combinations.Only the owner/admin of the OApp can call this function.Provides a way for the OApp to enforce things like paying for PreCrime, AND/OR minimum dst lzReceive gas amounts etc.These enforced options can vary as the potential options/execution on the remote may differ as per the msgType. eg. Amount of lzReceive() gas necessary to deliver a lzCompose() message adds overhead you dont want to pay if you are only making a standard LayerZero message ie. lzReceive() WITHOUT sendCompose().\",\"params\":{\"_enforcedOptions\":\"An array of EnforcedOptionParam structures specifying enforced options.\"}},\"setMsgInspector(address)\":{\"details\":\"Sets the message inspector address for the OFT.This is an optional contract that can be used to inspect both 'message' and 'options'.Set it to address(0) to disable it, or set it to a contract address to enable it.\",\"params\":{\"_msgInspector\":\"The address of the message inspector.\"}},\"setPeer(uint32,bytes32)\":{\"details\":\"Only the owner/admin of the OApp can call this function.Indicates that the peer is trusted to send LayerZero messages to this OApp.Set this to bytes32(0) to remove the peer address.Peer is a bytes32 to accommodate non-evm chains.\",\"params\":{\"_eid\":\"The endpoint ID.\",\"_peer\":\"The address of the peer to be associated with the corresponding endpoint.\"}},\"setPreCrime(address)\":{\"details\":\"Sets the preCrime contract address.\",\"params\":{\"_preCrime\":\"The address of the preCrime contract.\"}},\"sharedDecimals()\":{\"details\":\"Retrieves the shared decimals of the OFT.Sets an implicit cap on the amount of tokens, over uint64.max() will need some sort of outbound cap / totalSupply cap Lowest common decimal denominator between chains. Defaults to 6 decimal places to provide up to 18,446,744,073,709.551615 units (max uint64). For tokens exceeding this totalSupply(), they will need to override the sharedDecimals function with something smaller. ie. 4 sharedDecimals would be 1,844,674,407,370,955.1615\",\"returns\":{\"_0\":\"The shared decimals of the OFT.\"}},\"symbol()\":{\"details\":\"Returns the symbol of the token, usually a shorter version of the name.\"},\"token()\":{\"details\":\"Retrieves the address of the underlying ERC20 implementation.In the case of OFT, address(this) and erc20 are the same contract.\",\"returns\":{\"_0\":\"The address of the OFT token.\"}},\"totalSupply()\":{\"details\":\"Returns the value of tokens in existence.\"},\"transfer(address,uint256)\":{\"details\":\"See {IERC20-transfer}. Requirements: - `to` cannot be the zero address. - the caller must have a balance of at least `value`.\"},\"transferFrom(address,address,uint256)\":{\"details\":\"See {IERC20-transferFrom}. Skips emitting an {Approval} event indicating an allowance update. This is not required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve]. NOTE: Does not update the allowance if the current allowance is the maximum `uint256`. Requirements: - `from` and `to` cannot be the zero address. - `from` must have a balance of at least `value`. - the caller must have allowance for ``from``'s tokens of at least `value`.\"},\"transferOwnership(address)\":{\"details\":\"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"allowInitializePath((uint32,bytes32,uint64))\":{\"notice\":\"Checks if the path initialization is allowed based on the provided origin.\"},\"approvalRequired()\":{\"notice\":\"Indicates whether the OFT contract requires approval of the 'token()' to send.\"},\"combineOptions(uint32,uint16,bytes)\":{\"notice\":\"Combines options for a given endpoint and message type.\"},\"endpoint()\":{\"notice\":\"Retrieves the LayerZero endpoint associated with the OApp.\"},\"isComposeMsgSender((uint32,bytes32,uint64),bytes,address)\":{\"notice\":\"Indicates whether an address is an approved composeMsg sender to the Endpoint.\"},\"mint(address,uint256)\":{\"notice\":\"Open mint, TESTNET ONLY \\u2014 same as ToyOFT, so the demo tasks work against either.\"},\"nextNonce(uint32,bytes32)\":{\"notice\":\"Retrieves the next nonce for a given source endpoint and sender address.\"},\"oAppVersion()\":{\"notice\":\"Retrieves the OApp version information.\"},\"oftVersion()\":{\"notice\":\"Retrieves interfaceID and the version of the OFT.\"},\"peers(uint32)\":{\"notice\":\"Retrieves the peer (OApp) associated with a corresponding endpoint.\"},\"quoteOFT((uint32,bytes32,uint256,uint256,bytes,bytes,bytes))\":{\"notice\":\"Provides the fee breakdown and settings data for an OFT. Unused in the default implementation.\"},\"quoteSend((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),bool)\":{\"notice\":\"Provides a quote for the send() operation.\"},\"setDelegate(address)\":{\"notice\":\"Sets the delegate address for the OApp.\"},\"setPeer(uint32,bytes32)\":{\"notice\":\"Sets the peer address (OApp instance) for a corresponding endpoint.\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/MyOFT.sol\":\"MyOFT\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\nimport { IMessageLibManager } from \\\"./IMessageLibManager.sol\\\";\\nimport { IMessagingComposer } from \\\"./IMessagingComposer.sol\\\";\\nimport { IMessagingChannel } from \\\"./IMessagingChannel.sol\\\";\\nimport { IMessagingContext } from \\\"./IMessagingContext.sol\\\";\\n\\nstruct MessagingParams {\\n uint32 dstEid;\\n bytes32 receiver;\\n bytes message;\\n bytes options;\\n bool payInLzToken;\\n}\\n\\nstruct MessagingReceipt {\\n bytes32 guid;\\n uint64 nonce;\\n MessagingFee fee;\\n}\\n\\nstruct MessagingFee {\\n uint256 nativeFee;\\n uint256 lzTokenFee;\\n}\\n\\nstruct Origin {\\n uint32 srcEid;\\n bytes32 sender;\\n uint64 nonce;\\n}\\n\\ninterface ILayerZeroEndpointV2 is IMessageLibManager, IMessagingComposer, IMessagingChannel, IMessagingContext {\\n event PacketSent(bytes encodedPayload, bytes options, address sendLibrary);\\n\\n event PacketVerified(Origin origin, address receiver, bytes32 payloadHash);\\n\\n event PacketDelivered(Origin origin, address receiver);\\n\\n event LzReceiveAlert(\\n address indexed receiver,\\n address indexed executor,\\n Origin origin,\\n bytes32 guid,\\n uint256 gas,\\n uint256 value,\\n bytes message,\\n bytes extraData,\\n bytes reason\\n );\\n\\n event LzTokenSet(address token);\\n\\n event DelegateSet(address sender, address delegate);\\n\\n function quote(MessagingParams calldata _params, address _sender) external view returns (MessagingFee memory);\\n\\n function send(\\n MessagingParams calldata _params,\\n address _refundAddress\\n ) external payable returns (MessagingReceipt memory);\\n\\n function verify(Origin calldata _origin, address _receiver, bytes32 _payloadHash) external;\\n\\n function verifiable(Origin calldata _origin, address _receiver) external view returns (bool);\\n\\n function initializable(Origin calldata _origin, address _receiver) external view returns (bool);\\n\\n function lzReceive(\\n Origin calldata _origin,\\n address _receiver,\\n bytes32 _guid,\\n bytes calldata _message,\\n bytes calldata _extraData\\n ) external payable;\\n\\n // oapp can burn messages partially by calling this function with its own business logic if messages are verified in order\\n function clear(address _oapp, Origin calldata _origin, bytes32 _guid, bytes calldata _message) external;\\n\\n function setLzToken(address _lzToken) external;\\n\\n function lzToken() external view returns (address);\\n\\n function nativeToken() external view returns (address);\\n\\n function setDelegate(address _delegate) external;\\n}\\n\",\"keccak256\":\"0xf7f941bee89ea6369950fe54e8ac476ae6478b958b20fc0e8a83e8ff1364eac3\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroReceiver.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\nimport { Origin } from \\\"./ILayerZeroEndpointV2.sol\\\";\\n\\ninterface ILayerZeroReceiver {\\n function allowInitializePath(Origin calldata _origin) external view returns (bool);\\n\\n function nextNonce(uint32 _eid, bytes32 _sender) external view returns (uint64);\\n\\n function lzReceive(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address _executor,\\n bytes calldata _extraData\\n ) external payable;\\n}\\n\",\"keccak256\":\"0x9641abba8d53b08bb517d1b74801dd15ea7b84d77a6719085bd96c8ea94e3ca0\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLib.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\nimport { IERC165 } from \\\"@openzeppelin/contracts/utils/introspection/IERC165.sol\\\";\\n\\nimport { SetConfigParam } from \\\"./IMessageLibManager.sol\\\";\\n\\nenum MessageLibType {\\n Send,\\n Receive,\\n SendAndReceive\\n}\\n\\ninterface IMessageLib is IERC165 {\\n function setConfig(address _oapp, SetConfigParam[] calldata _config) external;\\n\\n function getConfig(uint32 _eid, address _oapp, uint32 _configType) external view returns (bytes memory config);\\n\\n function isSupportedEid(uint32 _eid) external view returns (bool);\\n\\n // message libs of same major version are compatible\\n function version() external view returns (uint64 major, uint8 minor, uint8 endpointVersion);\\n\\n function messageLibType() external view returns (MessageLibType);\\n}\\n\",\"keccak256\":\"0x5cf5f24751b4e3ea1c9c5ded07cedfdfd62566b6daaffcc0144733859c9dba0c\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLibManager.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\nstruct SetConfigParam {\\n uint32 eid;\\n uint32 configType;\\n bytes config;\\n}\\n\\ninterface IMessageLibManager {\\n struct Timeout {\\n address lib;\\n uint256 expiry;\\n }\\n\\n event LibraryRegistered(address newLib);\\n event DefaultSendLibrarySet(uint32 eid, address newLib);\\n event DefaultReceiveLibrarySet(uint32 eid, address newLib);\\n event DefaultReceiveLibraryTimeoutSet(uint32 eid, address oldLib, uint256 expiry);\\n event SendLibrarySet(address sender, uint32 eid, address newLib);\\n event ReceiveLibrarySet(address receiver, uint32 eid, address newLib);\\n event ReceiveLibraryTimeoutSet(address receiver, uint32 eid, address oldLib, uint256 timeout);\\n\\n function registerLibrary(address _lib) external;\\n\\n function isRegisteredLibrary(address _lib) external view returns (bool);\\n\\n function getRegisteredLibraries() external view returns (address[] memory);\\n\\n function setDefaultSendLibrary(uint32 _eid, address _newLib) external;\\n\\n function defaultSendLibrary(uint32 _eid) external view returns (address);\\n\\n function setDefaultReceiveLibrary(uint32 _eid, address _newLib, uint256 _gracePeriod) external;\\n\\n function defaultReceiveLibrary(uint32 _eid) external view returns (address);\\n\\n function setDefaultReceiveLibraryTimeout(uint32 _eid, address _lib, uint256 _expiry) external;\\n\\n function defaultReceiveLibraryTimeout(uint32 _eid) external view returns (address lib, uint256 expiry);\\n\\n function isSupportedEid(uint32 _eid) external view returns (bool);\\n\\n function isValidReceiveLibrary(address _receiver, uint32 _eid, address _lib) external view returns (bool);\\n\\n /// ------------------- OApp interfaces -------------------\\n function setSendLibrary(address _oapp, uint32 _eid, address _newLib) external;\\n\\n function getSendLibrary(address _sender, uint32 _eid) external view returns (address lib);\\n\\n function isDefaultSendLibrary(address _sender, uint32 _eid) external view returns (bool);\\n\\n function setReceiveLibrary(address _oapp, uint32 _eid, address _newLib, uint256 _gracePeriod) external;\\n\\n function getReceiveLibrary(address _receiver, uint32 _eid) external view returns (address lib, bool isDefault);\\n\\n function setReceiveLibraryTimeout(address _oapp, uint32 _eid, address _lib, uint256 _expiry) external;\\n\\n function receiveLibraryTimeout(address _receiver, uint32 _eid) external view returns (address lib, uint256 expiry);\\n\\n function setConfig(address _oapp, address _lib, SetConfigParam[] calldata _params) external;\\n\\n function getConfig(\\n address _oapp,\\n address _lib,\\n uint32 _eid,\\n uint32 _configType\\n ) external view returns (bytes memory config);\\n}\\n\",\"keccak256\":\"0x919b37133adff4dc528e3061deb2789c3149971b530c61e556fb3d09ab315dfc\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingChannel.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\ninterface IMessagingChannel {\\n event InboundNonceSkipped(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce);\\n event PacketNilified(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce, bytes32 payloadHash);\\n event PacketBurnt(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce, bytes32 payloadHash);\\n\\n function eid() external view returns (uint32);\\n\\n // this is an emergency function if a message cannot be verified for some reasons\\n // required to provide _nextNonce to avoid race condition\\n function skip(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce) external;\\n\\n function nilify(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes32 _payloadHash) external;\\n\\n function burn(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes32 _payloadHash) external;\\n\\n function nextGuid(address _sender, uint32 _dstEid, bytes32 _receiver) external view returns (bytes32);\\n\\n function inboundNonce(address _receiver, uint32 _srcEid, bytes32 _sender) external view returns (uint64);\\n\\n function outboundNonce(address _sender, uint32 _dstEid, bytes32 _receiver) external view returns (uint64);\\n\\n function inboundPayloadHash(\\n address _receiver,\\n uint32 _srcEid,\\n bytes32 _sender,\\n uint64 _nonce\\n ) external view returns (bytes32);\\n\\n function lazyInboundNonce(address _receiver, uint32 _srcEid, bytes32 _sender) external view returns (uint64);\\n}\\n\",\"keccak256\":\"0x0878f64dffebf58c4165569416372f40860fab546b88cd926eba0d5cb6d8d972\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingComposer.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\ninterface IMessagingComposer {\\n event ComposeSent(address from, address to, bytes32 guid, uint16 index, bytes message);\\n event ComposeDelivered(address from, address to, bytes32 guid, uint16 index);\\n event LzComposeAlert(\\n address indexed from,\\n address indexed to,\\n address indexed executor,\\n bytes32 guid,\\n uint16 index,\\n uint256 gas,\\n uint256 value,\\n bytes message,\\n bytes extraData,\\n bytes reason\\n );\\n\\n function composeQueue(\\n address _from,\\n address _to,\\n bytes32 _guid,\\n uint16 _index\\n ) external view returns (bytes32 messageHash);\\n\\n function sendCompose(address _to, bytes32 _guid, uint16 _index, bytes calldata _message) external;\\n\\n function lzCompose(\\n address _from,\\n address _to,\\n bytes32 _guid,\\n uint16 _index,\\n bytes calldata _message,\\n bytes calldata _extraData\\n ) external payable;\\n}\\n\",\"keccak256\":\"0x85bc7090134529ec474866dc4bb1c48692d518c756eb0a961c82574829c51901\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingContext.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\ninterface IMessagingContext {\\n function isSendingMessage() external view returns (bool);\\n\\n function getSendContext() external view returns (uint32 dstEid, address sender);\\n}\\n\",\"keccak256\":\"0xff0c546c2813dae3e440882f46b377375f7461b0714efd80bd3f0c6e5cb8da4e\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ISendLib.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.8.0;\\n\\nimport { MessagingFee } from \\\"./ILayerZeroEndpointV2.sol\\\";\\nimport { IMessageLib } from \\\"./IMessageLib.sol\\\";\\n\\nstruct Packet {\\n uint64 nonce;\\n uint32 srcEid;\\n address sender;\\n uint32 dstEid;\\n bytes32 receiver;\\n bytes32 guid;\\n bytes message;\\n}\\n\\ninterface ISendLib is IMessageLib {\\n function send(\\n Packet calldata _packet,\\n bytes calldata _options,\\n bool _payInLzToken\\n ) external returns (MessagingFee memory, bytes memory encodedPacket);\\n\\n function quote(\\n Packet calldata _packet,\\n bytes calldata _options,\\n bool _payInLzToken\\n ) external view returns (MessagingFee memory);\\n\\n function setTreasury(address _treasury) external;\\n\\n function withdrawFee(address _to, uint256 _amount) external;\\n\\n function withdrawLzTokenFee(address _lzToken, address _to, uint256 _amount) external;\\n}\\n\",\"keccak256\":\"0xf1c07bc61e7b1dce195ed12d50f87980fbf2d63cac1326fd28287f55fe0ba625\",\"license\":\"MIT\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/libs/AddressCast.sol\":{\"content\":\"// SPDX-License-Identifier: LZBL-1.2\\n\\npragma solidity ^0.8.20;\\n\\nlibrary AddressCast {\\n error AddressCast_InvalidSizeForAddress();\\n error AddressCast_InvalidAddress();\\n\\n function toBytes32(bytes calldata _addressBytes) internal pure returns (bytes32 result) {\\n if (_addressBytes.length > 32) revert AddressCast_InvalidAddress();\\n result = bytes32(_addressBytes);\\n unchecked {\\n uint256 offset = 32 - _addressBytes.length;\\n result = result >> (offset * 8);\\n }\\n }\\n\\n function toBytes32(address _address) internal pure returns (bytes32 result) {\\n result = bytes32(uint256(uint160(_address)));\\n }\\n\\n function toBytes(bytes32 _addressBytes32, uint256 _size) internal pure returns (bytes memory result) {\\n if (_size == 0 || _size > 32) revert AddressCast_InvalidSizeForAddress();\\n result = new bytes(_size);\\n unchecked {\\n uint256 offset = 256 - _size * 8;\\n assembly {\\n mstore(add(result, 32), shl(offset, _addressBytes32))\\n }\\n }\\n }\\n\\n function toAddress(bytes32 _addressBytes32) internal pure returns (address result) {\\n result = address(uint160(uint256(_addressBytes32)));\\n }\\n\\n function toAddress(bytes calldata _addressBytes) internal pure returns (address result) {\\n if (_addressBytes.length != 20) revert AddressCast_InvalidAddress();\\n result = address(bytes20(_addressBytes));\\n }\\n}\\n\",\"keccak256\":\"0x2ebbcaaab3554edcd41b581f1a72ac1806afbfb8047d0d47ff098f9af30d6deb\",\"license\":\"LZBL-1.2\"},\"@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.sol\":{\"content\":\"// SPDX-License-Identifier: LZBL-1.2\\n\\npragma solidity ^0.8.20;\\n\\nimport { Packet } from \\\"../../interfaces/ISendLib.sol\\\";\\nimport { AddressCast } from \\\"../../libs/AddressCast.sol\\\";\\n\\nlibrary PacketV1Codec {\\n using AddressCast for address;\\n using AddressCast for bytes32;\\n\\n uint8 internal constant PACKET_VERSION = 1;\\n\\n // header (version + nonce + path)\\n // version\\n uint256 private constant PACKET_VERSION_OFFSET = 0;\\n // nonce\\n uint256 private constant NONCE_OFFSET = 1;\\n // path\\n uint256 private constant SRC_EID_OFFSET = 9;\\n uint256 private constant SENDER_OFFSET = 13;\\n uint256 private constant DST_EID_OFFSET = 45;\\n uint256 private constant RECEIVER_OFFSET = 49;\\n // payload (guid + message)\\n uint256 private constant GUID_OFFSET = 81; // keccak256(nonce + path)\\n uint256 private constant MESSAGE_OFFSET = 113;\\n\\n function encode(Packet memory _packet) internal pure returns (bytes memory encodedPacket) {\\n encodedPacket = abi.encodePacked(\\n PACKET_VERSION,\\n _packet.nonce,\\n _packet.srcEid,\\n _packet.sender.toBytes32(),\\n _packet.dstEid,\\n _packet.receiver,\\n _packet.guid,\\n _packet.message\\n );\\n }\\n\\n function encodePacketHeader(Packet memory _packet) internal pure returns (bytes memory) {\\n return\\n abi.encodePacked(\\n PACKET_VERSION,\\n _packet.nonce,\\n _packet.srcEid,\\n _packet.sender.toBytes32(),\\n _packet.dstEid,\\n _packet.receiver\\n );\\n }\\n\\n function encodePayload(Packet memory _packet) internal pure returns (bytes memory) {\\n return abi.encodePacked(_packet.guid, _packet.message);\\n }\\n\\n function header(bytes calldata _packet) internal pure returns (bytes calldata) {\\n return _packet[0:GUID_OFFSET];\\n }\\n\\n function version(bytes calldata _packet) internal pure returns (uint8) {\\n return uint8(bytes1(_packet[PACKET_VERSION_OFFSET:NONCE_OFFSET]));\\n }\\n\\n function nonce(bytes calldata _packet) internal pure returns (uint64) {\\n return uint64(bytes8(_packet[NONCE_OFFSET:SRC_EID_OFFSET]));\\n }\\n\\n function srcEid(bytes calldata _packet) internal pure returns (uint32) {\\n return uint32(bytes4(_packet[SRC_EID_OFFSET:SENDER_OFFSET]));\\n }\\n\\n function sender(bytes calldata _packet) internal pure returns (bytes32) {\\n return bytes32(_packet[SENDER_OFFSET:DST_EID_OFFSET]);\\n }\\n\\n function senderAddressB20(bytes calldata _packet) internal pure returns (address) {\\n return sender(_packet).toAddress();\\n }\\n\\n function dstEid(bytes calldata _packet) internal pure returns (uint32) {\\n return uint32(bytes4(_packet[DST_EID_OFFSET:RECEIVER_OFFSET]));\\n }\\n\\n function receiver(bytes calldata _packet) internal pure returns (bytes32) {\\n return bytes32(_packet[RECEIVER_OFFSET:GUID_OFFSET]);\\n }\\n\\n function receiverB20(bytes calldata _packet) internal pure returns (address) {\\n return receiver(_packet).toAddress();\\n }\\n\\n function guid(bytes calldata _packet) internal pure returns (bytes32) {\\n return bytes32(_packet[GUID_OFFSET:MESSAGE_OFFSET]);\\n }\\n\\n function message(bytes calldata _packet) internal pure returns (bytes calldata) {\\n return bytes(_packet[MESSAGE_OFFSET:]);\\n }\\n\\n function payload(bytes calldata _packet) internal pure returns (bytes calldata) {\\n return bytes(_packet[GUID_OFFSET:]);\\n }\\n\\n function payloadHash(bytes calldata _packet) internal pure returns (bytes32) {\\n return keccak256(payload(_packet));\\n }\\n}\\n\",\"keccak256\":\"0xc84cf1bf785977fe1fbe7566eef902c2db68d0e163813ebe6c34921754802680\",\"license\":\"LZBL-1.2\"},\"@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\n// @dev Import the 'MessagingFee' and 'MessagingReceipt' so it's exposed to OApp implementers\\n// solhint-disable-next-line no-unused-import\\nimport { OAppSender, MessagingFee, MessagingReceipt } from \\\"./OAppSender.sol\\\";\\n// @dev Import the 'Origin' so it's exposed to OApp implementers\\n// solhint-disable-next-line no-unused-import\\nimport { OAppReceiver, Origin } from \\\"./OAppReceiver.sol\\\";\\nimport { OAppCore } from \\\"./OAppCore.sol\\\";\\n\\n/**\\n * @title OApp\\n * @dev Abstract contract serving as the base for OApp implementation, combining OAppSender and OAppReceiver functionality.\\n */\\nabstract contract OApp is OAppSender, OAppReceiver {\\n /**\\n * @dev Constructor to initialize the OApp with the provided endpoint and owner.\\n * @param _endpoint The address of the LOCAL LayerZero endpoint.\\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\\n */\\n constructor(address _endpoint, address _delegate) OAppCore(_endpoint, _delegate) {}\\n\\n /**\\n * @notice Retrieves the OApp version information.\\n * @return senderVersion The version of the OAppSender.sol implementation.\\n * @return receiverVersion The version of the OAppReceiver.sol implementation.\\n */\\n function oAppVersion()\\n public\\n pure\\n virtual\\n override(OAppSender, OAppReceiver)\\n returns (uint64 senderVersion, uint64 receiverVersion)\\n {\\n return (SENDER_VERSION, RECEIVER_VERSION);\\n }\\n}\\n\",\"keccak256\":\"0xac362c4c291fad2f1511a968424b2e78a5ad502d1c867bd31da04be742aca8c5\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/OAppCore.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { Ownable } from \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\nimport { IOAppCore, ILayerZeroEndpointV2 } from \\\"./interfaces/IOAppCore.sol\\\";\\n\\n/**\\n * @title OAppCore\\n * @dev Abstract contract implementing the IOAppCore interface with basic OApp configurations.\\n */\\nabstract contract OAppCore is IOAppCore, Ownable {\\n // The LayerZero endpoint associated with the given OApp\\n ILayerZeroEndpointV2 public immutable endpoint;\\n\\n // Mapping to store peers associated with corresponding endpoints\\n mapping(uint32 eid => bytes32 peer) public peers;\\n\\n /**\\n * @dev Constructor to initialize the OAppCore with the provided endpoint and delegate.\\n * @param _endpoint The address of the LOCAL Layer Zero endpoint.\\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\\n *\\n * @dev The delegate typically should be set as the owner of the contract.\\n */\\n constructor(address _endpoint, address _delegate) {\\n endpoint = ILayerZeroEndpointV2(_endpoint);\\n\\n if (_delegate == address(0)) revert InvalidDelegate();\\n endpoint.setDelegate(_delegate);\\n }\\n\\n /**\\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\\n * @param _eid The endpoint ID.\\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\\n *\\n * @dev Only the owner/admin of the OApp can call this function.\\n * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp.\\n * @dev Set this to bytes32(0) to remove the peer address.\\n * @dev Peer is a bytes32 to accommodate non-evm chains.\\n */\\n function setPeer(uint32 _eid, bytes32 _peer) public virtual onlyOwner {\\n _setPeer(_eid, _peer);\\n }\\n\\n /**\\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\\n * @param _eid The endpoint ID.\\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\\n *\\n * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp.\\n * @dev Set this to bytes32(0) to remove the peer address.\\n * @dev Peer is a bytes32 to accommodate non-evm chains.\\n */\\n function _setPeer(uint32 _eid, bytes32 _peer) internal virtual {\\n peers[_eid] = _peer;\\n emit PeerSet(_eid, _peer);\\n }\\n\\n /**\\n * @notice Internal function to get the peer address associated with a specific endpoint; reverts if NOT set.\\n * ie. the peer is set to bytes32(0).\\n * @param _eid The endpoint ID.\\n * @return peer The address of the peer associated with the specified endpoint.\\n */\\n function _getPeerOrRevert(uint32 _eid) internal view virtual returns (bytes32) {\\n bytes32 peer = peers[_eid];\\n if (peer == bytes32(0)) revert NoPeer(_eid);\\n return peer;\\n }\\n\\n /**\\n * @notice Sets the delegate address for the OApp.\\n * @param _delegate The address of the delegate to be set.\\n *\\n * @dev Only the owner/admin of the OApp can call this function.\\n * @dev Provides the ability for a delegate to set configs, on behalf of the OApp, directly on the Endpoint contract.\\n */\\n function setDelegate(address _delegate) public onlyOwner {\\n endpoint.setDelegate(_delegate);\\n }\\n}\\n\",\"keccak256\":\"0x13a9c2d1d2c1f086b8624f2e84c4a4702212daae36f701d92bb915b535cbe4cc\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/OAppReceiver.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { IOAppReceiver, Origin } from \\\"./interfaces/IOAppReceiver.sol\\\";\\nimport { OAppCore } from \\\"./OAppCore.sol\\\";\\n\\n/**\\n * @title OAppReceiver\\n * @dev Abstract contract implementing the ILayerZeroReceiver interface and extending OAppCore for OApp receivers.\\n */\\nabstract contract OAppReceiver is IOAppReceiver, OAppCore {\\n // Custom error message for when the caller is not the registered endpoint/\\n error OnlyEndpoint(address addr);\\n\\n // @dev The version of the OAppReceiver implementation.\\n // @dev Version is bumped when changes are made to this contract.\\n uint64 internal constant RECEIVER_VERSION = 2;\\n\\n /**\\n * @notice Retrieves the OApp version information.\\n * @return senderVersion The version of the OAppSender.sol contract.\\n * @return receiverVersion The version of the OAppReceiver.sol contract.\\n *\\n * @dev Providing 0 as the default for OAppSender version. Indicates that the OAppSender is not implemented.\\n * ie. this is a RECEIVE only OApp.\\n * @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions.\\n */\\n function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) {\\n return (0, RECEIVER_VERSION);\\n }\\n\\n /**\\n * @notice Indicates whether an address is an approved composeMsg sender to the Endpoint.\\n * @dev _origin The origin information containing the source endpoint and sender address.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address on the src chain.\\n * - nonce: The nonce of the message.\\n * @dev _message The lzReceive payload.\\n * @param _sender The sender address.\\n * @return isSender Is a valid sender.\\n *\\n * @dev Applications can optionally choose to implement separate composeMsg senders that are NOT the bridging layer.\\n * @dev The default sender IS the OAppReceiver implementer.\\n */\\n function isComposeMsgSender(\\n Origin calldata /*_origin*/,\\n bytes calldata /*_message*/,\\n address _sender\\n ) public view virtual returns (bool) {\\n return _sender == address(this);\\n }\\n\\n /**\\n * @notice Checks if the path initialization is allowed based on the provided origin.\\n * @param origin The origin information containing the source endpoint and sender address.\\n * @return Whether the path has been initialized.\\n *\\n * @dev This indicates to the endpoint that the OApp has enabled msgs for this particular path to be received.\\n * @dev This defaults to assuming if a peer has been set, its initialized.\\n * Can be overridden by the OApp if there is other logic to determine this.\\n */\\n function allowInitializePath(Origin calldata origin) public view virtual returns (bool) {\\n return peers[origin.srcEid] == origin.sender;\\n }\\n\\n /**\\n * @notice Retrieves the next nonce for a given source endpoint and sender address.\\n * @dev _srcEid The source endpoint ID.\\n * @dev _sender The sender address.\\n * @return nonce The next nonce.\\n *\\n * @dev The path nonce starts from 1. If 0 is returned it means that there is NO nonce ordered enforcement.\\n * @dev Is required by the off-chain executor to determine the OApp expects msg execution is ordered.\\n * @dev This is also enforced by the OApp.\\n * @dev By default this is NOT enabled. ie. nextNonce is hardcoded to return 0.\\n */\\n function nextNonce(uint32 /*_srcEid*/, bytes32 /*_sender*/) public view virtual returns (uint64 nonce) {\\n return 0;\\n }\\n\\n /**\\n * @dev Entry point for receiving messages or packets from the endpoint.\\n * @param _origin The origin information containing the source endpoint and sender address.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address on the src chain.\\n * - nonce: The nonce of the message.\\n * @param _guid The unique identifier for the received LayerZero message.\\n * @param _message The payload of the received message.\\n * @param _executor The address of the executor for the received message.\\n * @param _extraData Additional arbitrary data provided by the corresponding executor.\\n *\\n * @dev Entry point for receiving msg/packet from the LayerZero endpoint.\\n */\\n function lzReceive(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address _executor,\\n bytes calldata _extraData\\n ) public payable virtual {\\n // Ensures that only the endpoint can attempt to lzReceive() messages to this OApp.\\n if (address(endpoint) != msg.sender) revert OnlyEndpoint(msg.sender);\\n\\n // Ensure that the sender matches the expected peer for the source endpoint.\\n if (_getPeerOrRevert(_origin.srcEid) != _origin.sender) revert OnlyPeer(_origin.srcEid, _origin.sender);\\n\\n // Call the internal OApp implementation of lzReceive.\\n _lzReceive(_origin, _guid, _message, _executor, _extraData);\\n }\\n\\n /**\\n * @dev Internal function to implement lzReceive logic without needing to copy the basic parameter validation.\\n */\\n function _lzReceive(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address _executor,\\n bytes calldata _extraData\\n ) internal virtual;\\n}\\n\",\"keccak256\":\"0x0174e9f1ec4cefe4b5adc26c392269c699b9ff75965364e5b7264426a462c70b\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/OAppSender.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { SafeERC20, IERC20 } from \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\nimport { MessagingParams, MessagingFee, MessagingReceipt } from \\\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\\\";\\nimport { OAppCore } from \\\"./OAppCore.sol\\\";\\n\\n/**\\n * @title OAppSender\\n * @dev Abstract contract implementing the OAppSender functionality for sending messages to a LayerZero endpoint.\\n */\\nabstract contract OAppSender is OAppCore {\\n using SafeERC20 for IERC20;\\n\\n // Custom error messages\\n error NotEnoughNative(uint256 msgValue);\\n error LzTokenUnavailable();\\n\\n // @dev The version of the OAppSender implementation.\\n // @dev Version is bumped when changes are made to this contract.\\n uint64 internal constant SENDER_VERSION = 1;\\n\\n /**\\n * @notice Retrieves the OApp version information.\\n * @return senderVersion The version of the OAppSender.sol contract.\\n * @return receiverVersion The version of the OAppReceiver.sol contract.\\n *\\n * @dev Providing 0 as the default for OAppReceiver version. Indicates that the OAppReceiver is not implemented.\\n * ie. this is a SEND only OApp.\\n * @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions\\n */\\n function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) {\\n return (SENDER_VERSION, 0);\\n }\\n\\n /**\\n * @dev Internal function to interact with the LayerZero EndpointV2.quote() for fee calculation.\\n * @param _dstEid The destination endpoint ID.\\n * @param _message The message payload.\\n * @param _options Additional options for the message.\\n * @param _payInLzToken Flag indicating whether to pay the fee in LZ tokens.\\n * @return fee The calculated MessagingFee for the message.\\n * - nativeFee: The native fee for the message.\\n * - lzTokenFee: The LZ token fee for the message.\\n */\\n function _quote(\\n uint32 _dstEid,\\n bytes memory _message,\\n bytes memory _options,\\n bool _payInLzToken\\n ) internal view virtual returns (MessagingFee memory fee) {\\n return\\n endpoint.quote(\\n MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _payInLzToken),\\n address(this)\\n );\\n }\\n\\n /**\\n * @dev Internal function to interact with the LayerZero EndpointV2.send() for sending a message.\\n * @param _dstEid The destination endpoint ID.\\n * @param _message The message payload.\\n * @param _options Additional options for the message.\\n * @param _fee The calculated LayerZero fee for the message.\\n * - nativeFee: The native fee.\\n * - lzTokenFee: The lzToken fee.\\n * @param _refundAddress The address to receive any excess fee values sent to the endpoint.\\n * @return receipt The receipt for the sent message.\\n * - guid: The unique identifier for the sent message.\\n * - nonce: The nonce of the sent message.\\n * - fee: The LayerZero fee incurred for the message.\\n */\\n function _lzSend(\\n uint32 _dstEid,\\n bytes memory _message,\\n bytes memory _options,\\n MessagingFee memory _fee,\\n address _refundAddress\\n ) internal virtual returns (MessagingReceipt memory receipt) {\\n // @dev Push corresponding fees to the endpoint, any excess is sent back to the _refundAddress from the endpoint.\\n uint256 messageValue = _payNative(_fee.nativeFee);\\n if (_fee.lzTokenFee > 0) _payLzToken(_fee.lzTokenFee);\\n\\n return\\n // solhint-disable-next-line check-send-result\\n endpoint.send{ value: messageValue }(\\n MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _fee.lzTokenFee > 0),\\n _refundAddress\\n );\\n }\\n\\n /**\\n * @dev Internal function to pay the native fee associated with the message.\\n * @param _nativeFee The native fee to be paid.\\n * @return nativeFee The amount of native currency paid.\\n *\\n * @dev If the OApp needs to initiate MULTIPLE LayerZero messages in a single transaction,\\n * this will need to be overridden because msg.value would contain multiple lzFees.\\n * @dev Should be overridden in the event the LayerZero endpoint requires a different native currency.\\n * @dev Some EVMs use an ERC20 as a method for paying transactions/gasFees.\\n * @dev The endpoint is EITHER/OR, ie. it will NOT support both types of native payment at a time.\\n */\\n function _payNative(uint256 _nativeFee) internal virtual returns (uint256 nativeFee) {\\n if (msg.value != _nativeFee) revert NotEnoughNative(msg.value);\\n return _nativeFee;\\n }\\n\\n /**\\n * @dev Internal function to pay the LZ token fee associated with the message.\\n * @param _lzTokenFee The LZ token fee to be paid.\\n *\\n * @dev If the caller is trying to pay in the specified lzToken, then the lzTokenFee is passed to the endpoint.\\n * @dev Any excess sent, is passed back to the specified _refundAddress in the _lzSend().\\n */\\n function _payLzToken(uint256 _lzTokenFee) internal virtual {\\n // @dev Cannot cache the token because it is not immutable in the endpoint.\\n address lzToken = endpoint.lzToken();\\n if (lzToken == address(0)) revert LzTokenUnavailable();\\n\\n // Pay LZ token fee by sending tokens to the endpoint.\\n IERC20(lzToken).safeTransferFrom(msg.sender, address(endpoint), _lzTokenFee);\\n }\\n}\\n\",\"keccak256\":\"0x518cf4adca601923ed4baa6619846a253ea32b8d8775f8bc1faa3dfac7f67c20\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppCore.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { ILayerZeroEndpointV2 } from \\\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\\\";\\n\\n/**\\n * @title IOAppCore\\n */\\ninterface IOAppCore {\\n // Custom error messages\\n error OnlyPeer(uint32 eid, bytes32 sender);\\n error NoPeer(uint32 eid);\\n error InvalidEndpointCall();\\n error InvalidDelegate();\\n\\n // Event emitted when a peer (OApp) is set for a corresponding endpoint\\n event PeerSet(uint32 eid, bytes32 peer);\\n\\n /**\\n * @notice Retrieves the OApp version information.\\n * @return senderVersion The version of the OAppSender.sol contract.\\n * @return receiverVersion The version of the OAppReceiver.sol contract.\\n */\\n function oAppVersion() external view returns (uint64 senderVersion, uint64 receiverVersion);\\n\\n /**\\n * @notice Retrieves the LayerZero endpoint associated with the OApp.\\n * @return iEndpoint The LayerZero endpoint as an interface.\\n */\\n function endpoint() external view returns (ILayerZeroEndpointV2 iEndpoint);\\n\\n /**\\n * @notice Retrieves the peer (OApp) associated with a corresponding endpoint.\\n * @param _eid The endpoint ID.\\n * @return peer The peer address (OApp instance) associated with the corresponding endpoint.\\n */\\n function peers(uint32 _eid) external view returns (bytes32 peer);\\n\\n /**\\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\\n * @param _eid The endpoint ID.\\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\\n */\\n function setPeer(uint32 _eid, bytes32 _peer) external;\\n\\n /**\\n * @notice Sets the delegate address for the OApp Core.\\n * @param _delegate The address of the delegate to be set.\\n */\\n function setDelegate(address _delegate) external;\\n}\\n\",\"keccak256\":\"0x40e49f2de74506e1da5dcaed53a39853f691647f4ceb0fccc8f49a68d3f47c58\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppMsgInspector.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\n/**\\n * @title IOAppMsgInspector\\n * @dev Interface for the OApp Message Inspector, allowing examination of message and options contents.\\n */\\ninterface IOAppMsgInspector {\\n // Custom error message for inspection failure\\n error InspectionFailed(bytes message, bytes options);\\n\\n /**\\n * @notice Allows the inspector to examine LayerZero message contents and optionally throw a revert if invalid.\\n * @param _message The message payload to be inspected.\\n * @param _options Additional options or parameters for inspection.\\n * @return valid A boolean indicating whether the inspection passed (true) or failed (false).\\n *\\n * @dev Optionally done as a revert, OR use the boolean provided to handle the failure.\\n */\\n function inspect(bytes calldata _message, bytes calldata _options) external view returns (bool valid);\\n}\\n\",\"keccak256\":\"0x339654e699043c400cad92de209aa23855ce10211c31cf4114042cc5224d3b7c\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppOptionsType3.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\n/**\\n * @dev Struct representing enforced option parameters.\\n */\\nstruct EnforcedOptionParam {\\n uint32 eid; // Endpoint ID\\n uint16 msgType; // Message Type\\n bytes options; // Additional options\\n}\\n\\n/**\\n * @title IOAppOptionsType3\\n * @dev Interface for the OApp with Type 3 Options, allowing the setting and combining of enforced options.\\n */\\ninterface IOAppOptionsType3 {\\n // Custom error message for invalid options\\n error InvalidOptions(bytes options);\\n\\n // Event emitted when enforced options are set\\n event EnforcedOptionSet(EnforcedOptionParam[] _enforcedOptions);\\n\\n /**\\n * @notice Sets enforced options for specific endpoint and message type combinations.\\n * @param _enforcedOptions An array of EnforcedOptionParam structures specifying enforced options.\\n */\\n function setEnforcedOptions(EnforcedOptionParam[] calldata _enforcedOptions) external;\\n\\n /**\\n * @notice Combines options for a given endpoint and message type.\\n * @param _eid The endpoint ID.\\n * @param _msgType The OApp message type.\\n * @param _extraOptions Additional options passed by the caller.\\n * @return options The combination of caller specified options AND enforced options.\\n */\\n function combineOptions(\\n uint32 _eid,\\n uint16 _msgType,\\n bytes calldata _extraOptions\\n ) external view returns (bytes memory options);\\n}\\n\",\"keccak256\":\"0x9fc08a51e9d7c9c710c4eb26f84fe77228305ad7da63fa486ff24ebf2f3bc461\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppReceiver.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.20;\\n\\nimport { ILayerZeroReceiver, Origin } from \\\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroReceiver.sol\\\";\\n\\ninterface IOAppReceiver is ILayerZeroReceiver {\\n /**\\n * @notice Indicates whether an address is an approved composeMsg sender to the Endpoint.\\n * @param _origin The origin information containing the source endpoint and sender address.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address on the src chain.\\n * - nonce: The nonce of the message.\\n * @param _message The lzReceive payload.\\n * @param _sender The sender address.\\n * @return isSender Is a valid sender.\\n *\\n * @dev Applications can optionally choose to implement a separate composeMsg sender that is NOT the bridging layer.\\n * @dev The default sender IS the OAppReceiver implementer.\\n */\\n function isComposeMsgSender(\\n Origin calldata _origin,\\n bytes calldata _message,\\n address _sender\\n ) external view returns (bool isSender);\\n}\\n\",\"keccak256\":\"0xd26135185e19b3732746d4a9e2923e896f28dec8664bab161faea2ee26fcdc3d\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { Ownable } from \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\nimport { IOAppOptionsType3, EnforcedOptionParam } from \\\"../interfaces/IOAppOptionsType3.sol\\\";\\n\\n/**\\n * @title OAppOptionsType3\\n * @dev Abstract contract implementing the IOAppOptionsType3 interface with type 3 options.\\n */\\nabstract contract OAppOptionsType3 is IOAppOptionsType3, Ownable {\\n uint16 internal constant OPTION_TYPE_3 = 3;\\n\\n // @dev The \\\"msgType\\\" should be defined in the child contract.\\n mapping(uint32 eid => mapping(uint16 msgType => bytes enforcedOption)) public enforcedOptions;\\n\\n /**\\n * @dev Sets the enforced options for specific endpoint and message type combinations.\\n * @param _enforcedOptions An array of EnforcedOptionParam structures specifying enforced options.\\n *\\n * @dev Only the owner/admin of the OApp can call this function.\\n * @dev Provides a way for the OApp to enforce things like paying for PreCrime, AND/OR minimum dst lzReceive gas amounts etc.\\n * @dev These enforced options can vary as the potential options/execution on the remote may differ as per the msgType.\\n * eg. Amount of lzReceive() gas necessary to deliver a lzCompose() message adds overhead you dont want to pay\\n * if you are only making a standard LayerZero message ie. lzReceive() WITHOUT sendCompose().\\n */\\n function setEnforcedOptions(EnforcedOptionParam[] calldata _enforcedOptions) public virtual onlyOwner {\\n _setEnforcedOptions(_enforcedOptions);\\n }\\n\\n /**\\n * @dev Sets the enforced options for specific endpoint and message type combinations.\\n * @param _enforcedOptions An array of EnforcedOptionParam structures specifying enforced options.\\n *\\n * @dev Provides a way for the OApp to enforce things like paying for PreCrime, AND/OR minimum dst lzReceive gas amounts etc.\\n * @dev These enforced options can vary as the potential options/execution on the remote may differ as per the msgType.\\n * eg. Amount of lzReceive() gas necessary to deliver a lzCompose() message adds overhead you dont want to pay\\n * if you are only making a standard LayerZero message ie. lzReceive() WITHOUT sendCompose().\\n */\\n function _setEnforcedOptions(EnforcedOptionParam[] memory _enforcedOptions) internal virtual {\\n for (uint256 i = 0; i < _enforcedOptions.length; i++) {\\n // @dev Enforced options are only available for optionType 3, as type 1 and 2 dont support combining.\\n _assertOptionsType3(_enforcedOptions[i].options);\\n enforcedOptions[_enforcedOptions[i].eid][_enforcedOptions[i].msgType] = _enforcedOptions[i].options;\\n }\\n\\n emit EnforcedOptionSet(_enforcedOptions);\\n }\\n\\n /**\\n * @notice Combines options for a given endpoint and message type.\\n * @param _eid The endpoint ID.\\n * @param _msgType The OAPP message type.\\n * @param _extraOptions Additional options passed by the caller.\\n * @return options The combination of caller specified options AND enforced options.\\n *\\n * @dev If there is an enforced lzReceive option:\\n * - {gasLimit: 200k, msg.value: 1 ether} AND a caller supplies a lzReceive option: {gasLimit: 100k, msg.value: 0.5 ether}\\n * - The resulting options will be {gasLimit: 300k, msg.value: 1.5 ether} when the message is executed on the remote lzReceive() function.\\n * @dev This presence of duplicated options is handled off-chain in the verifier/executor.\\n */\\n function combineOptions(\\n uint32 _eid,\\n uint16 _msgType,\\n bytes calldata _extraOptions\\n ) public view virtual returns (bytes memory) {\\n bytes memory enforced = enforcedOptions[_eid][_msgType];\\n\\n // No enforced options, pass whatever the caller supplied, even if it's empty or legacy type 1/2 options.\\n if (enforced.length == 0) return _extraOptions;\\n\\n // No caller options, return enforced\\n if (_extraOptions.length == 0) return enforced;\\n\\n // @dev If caller provided _extraOptions, must be type 3 as its the ONLY type that can be combined.\\n if (_extraOptions.length >= 2) {\\n _assertOptionsType3(_extraOptions);\\n // @dev Remove the first 2 bytes containing the type from the _extraOptions and combine with enforced.\\n return bytes.concat(enforced, _extraOptions[2:]);\\n }\\n\\n // No valid set of options was found.\\n revert InvalidOptions(_extraOptions);\\n }\\n\\n /**\\n * @dev Internal function to assert that options are of type 3.\\n * @param _options The options to be checked.\\n */\\n function _assertOptionsType3(bytes memory _options) internal pure virtual {\\n uint16 optionsType;\\n assembly {\\n optionsType := mload(add(_options, 2))\\n }\\n if (optionsType != OPTION_TYPE_3) revert InvalidOptions(_options);\\n }\\n}\\n\",\"keccak256\":\"0x5275636cd47e660a2fdf6c7fe9d41ff3cc866b785cc8a9d88c1b8ca983509f01\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/precrime/OAppPreCrimeSimulator.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { Ownable } from \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\nimport { IPreCrime } from \\\"./interfaces/IPreCrime.sol\\\";\\nimport { IOAppPreCrimeSimulator, InboundPacket, Origin } from \\\"./interfaces/IOAppPreCrimeSimulator.sol\\\";\\n\\n/**\\n * @title OAppPreCrimeSimulator\\n * @dev Abstract contract serving as the base for preCrime simulation functionality in an OApp.\\n */\\nabstract contract OAppPreCrimeSimulator is IOAppPreCrimeSimulator, Ownable {\\n // The address of the preCrime implementation.\\n address public preCrime;\\n\\n /**\\n * @dev Retrieves the address of the OApp contract.\\n * @return The address of the OApp contract.\\n *\\n * @dev The simulator contract is the base contract for the OApp by default.\\n * @dev If the simulator is a separate contract, override this function.\\n */\\n function oApp() external view virtual returns (address) {\\n return address(this);\\n }\\n\\n /**\\n * @dev Sets the preCrime contract address.\\n * @param _preCrime The address of the preCrime contract.\\n */\\n function setPreCrime(address _preCrime) public virtual onlyOwner {\\n preCrime = _preCrime;\\n emit PreCrimeSet(_preCrime);\\n }\\n\\n /**\\n * @dev Interface for pre-crime simulations. Always reverts at the end with the simulation results.\\n * @param _packets An array of InboundPacket objects representing received packets to be delivered.\\n *\\n * @dev WARNING: MUST revert at the end with the simulation results.\\n * @dev Gives the preCrime implementation the ability to mock sending packets to the lzReceive function,\\n * WITHOUT actually executing them.\\n */\\n function lzReceiveAndRevert(InboundPacket[] calldata _packets) public payable virtual {\\n for (uint256 i = 0; i < _packets.length; i++) {\\n InboundPacket calldata packet = _packets[i];\\n\\n // Ignore packets that are not from trusted peers.\\n if (!isPeer(packet.origin.srcEid, packet.origin.sender)) continue;\\n\\n // @dev Because a verifier is calling this function, it doesnt have access to executor params:\\n // - address _executor\\n // - bytes calldata _extraData\\n // preCrime will NOT work for OApps that rely on these two parameters inside of their _lzReceive().\\n // They are instead stubbed to default values, address(0) and bytes(\\\"\\\")\\n // @dev Calling this.lzReceiveSimulate removes ability for assembly return 0 callstack exit,\\n // which would cause the revert to be ignored.\\n this.lzReceiveSimulate{ value: packet.value }(\\n packet.origin,\\n packet.guid,\\n packet.message,\\n packet.executor,\\n packet.extraData\\n );\\n }\\n\\n // @dev Revert with the simulation results. msg.sender must implement IPreCrime.buildSimulationResult().\\n revert SimulationResult(IPreCrime(msg.sender).buildSimulationResult());\\n }\\n\\n /**\\n * @dev Is effectively an internal function because msg.sender must be address(this).\\n * Allows resetting the call stack for 'internal' calls.\\n * @param _origin The origin information containing the source endpoint and sender address.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address on the src chain.\\n * - nonce: The nonce of the message.\\n * @param _guid The unique identifier of the packet.\\n * @param _message The message payload of the packet.\\n * @param _executor The executor address for the packet.\\n * @param _extraData Additional data for the packet.\\n */\\n function lzReceiveSimulate(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address _executor,\\n bytes calldata _extraData\\n ) external payable virtual {\\n // @dev Ensure ONLY can be called 'internally'.\\n if (msg.sender != address(this)) revert OnlySelf();\\n _lzReceiveSimulate(_origin, _guid, _message, _executor, _extraData);\\n }\\n\\n /**\\n * @dev Internal function to handle the OAppPreCrimeSimulator simulated receive.\\n * @param _origin The origin information.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address from the src chain.\\n * - nonce: The nonce of the LayerZero message.\\n * @param _guid The GUID of the LayerZero message.\\n * @param _message The LayerZero message.\\n * @param _executor The address of the off-chain executor.\\n * @param _extraData Arbitrary data passed by the msg executor.\\n *\\n * @dev Enables the preCrime simulator to mock sending lzReceive() messages,\\n * routes the msg down from the OAppPreCrimeSimulator, and back up to the OAppReceiver.\\n */\\n function _lzReceiveSimulate(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address _executor,\\n bytes calldata _extraData\\n ) internal virtual;\\n\\n /**\\n * @dev checks if the specified peer is considered 'trusted' by the OApp.\\n * @param _eid The endpoint Id to check.\\n * @param _peer The peer to check.\\n * @return Whether the peer passed is considered 'trusted' by the OApp.\\n */\\n function isPeer(uint32 _eid, bytes32 _peer) public view virtual returns (bool);\\n}\\n\",\"keccak256\":\"0x205a0abfd8b3c9af2740769f251381b84999b8e9347f3cd50de3ef8290a17750\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/precrime/interfaces/IOAppPreCrimeSimulator.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\n// @dev Import the Origin so it's exposed to OAppPreCrimeSimulator implementers.\\n// solhint-disable-next-line no-unused-import\\nimport { InboundPacket, Origin } from \\\"../libs/Packet.sol\\\";\\n\\n/**\\n * @title IOAppPreCrimeSimulator Interface\\n * @dev Interface for the preCrime simulation functionality in an OApp.\\n */\\ninterface IOAppPreCrimeSimulator {\\n // @dev simulation result used in PreCrime implementation\\n error SimulationResult(bytes result);\\n error OnlySelf();\\n\\n /**\\n * @dev Emitted when the preCrime contract address is set.\\n * @param preCrimeAddress The address of the preCrime contract.\\n */\\n event PreCrimeSet(address preCrimeAddress);\\n\\n /**\\n * @dev Retrieves the address of the preCrime contract implementation.\\n * @return The address of the preCrime contract.\\n */\\n function preCrime() external view returns (address);\\n\\n /**\\n * @dev Retrieves the address of the OApp contract.\\n * @return The address of the OApp contract.\\n */\\n function oApp() external view returns (address);\\n\\n /**\\n * @dev Sets the preCrime contract address.\\n * @param _preCrime The address of the preCrime contract.\\n */\\n function setPreCrime(address _preCrime) external;\\n\\n /**\\n * @dev Mocks receiving a packet, then reverts with a series of data to infer the state/result.\\n * @param _packets An array of LayerZero InboundPacket objects representing received packets.\\n */\\n function lzReceiveAndRevert(InboundPacket[] calldata _packets) external payable;\\n\\n /**\\n * @dev checks if the specified peer is considered 'trusted' by the OApp.\\n * @param _eid The endpoint Id to check.\\n * @param _peer The peer to check.\\n * @return Whether the peer passed is considered 'trusted' by the OApp.\\n */\\n function isPeer(uint32 _eid, bytes32 _peer) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x5d24db150949ea8e6437178e65a942e8c8b7f332e5daf32750f56b23b35b5bb2\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/precrime/interfaces/IPreCrime.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\nstruct PreCrimePeer {\\n uint32 eid;\\n bytes32 preCrime;\\n bytes32 oApp;\\n}\\n\\n// TODO not done yet\\ninterface IPreCrime {\\n error OnlyOffChain();\\n\\n // for simulate()\\n error PacketOversize(uint256 max, uint256 actual);\\n error PacketUnsorted();\\n error SimulationFailed(bytes reason);\\n\\n // for preCrime()\\n error SimulationResultNotFound(uint32 eid);\\n error InvalidSimulationResult(uint32 eid, bytes reason);\\n error CrimeFound(bytes crime);\\n\\n function getConfig(bytes[] calldata _packets, uint256[] calldata _packetMsgValues) external returns (bytes memory);\\n\\n function simulate(\\n bytes[] calldata _packets,\\n uint256[] calldata _packetMsgValues\\n ) external payable returns (bytes memory);\\n\\n function buildSimulationResult() external view returns (bytes memory);\\n\\n function preCrime(\\n bytes[] calldata _packets,\\n uint256[] calldata _packetMsgValues,\\n bytes[] calldata _simulations\\n ) external;\\n\\n function version() external view returns (uint64 major, uint8 minor);\\n}\\n\",\"keccak256\":\"0xc8d869f27ef8ceb2e13fdf6a70682fd4dee3f90c4924eb8e125bc1e66cb6af84\",\"license\":\"MIT\"},\"@layerzerolabs/oapp-evm/contracts/precrime/libs/Packet.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { Origin } from \\\"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\\\";\\nimport { PacketV1Codec } from \\\"@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.sol\\\";\\n\\n/**\\n * @title InboundPacket\\n * @dev Structure representing an inbound packet received by the contract.\\n */\\nstruct InboundPacket {\\n Origin origin; // Origin information of the packet.\\n uint32 dstEid; // Destination endpointId of the packet.\\n address receiver; // Receiver address for the packet.\\n bytes32 guid; // Unique identifier of the packet.\\n uint256 value; // msg.value of the packet.\\n address executor; // Executor address for the packet.\\n bytes message; // Message payload of the packet.\\n bytes extraData; // Additional arbitrary data for the packet.\\n}\\n\\n/**\\n * @title PacketDecoder\\n * @dev Library for decoding LayerZero packets.\\n */\\nlibrary PacketDecoder {\\n using PacketV1Codec for bytes;\\n\\n /**\\n * @dev Decode an inbound packet from the given packet data.\\n * @param _packet The packet data to decode.\\n * @return packet An InboundPacket struct representing the decoded packet.\\n */\\n function decode(bytes calldata _packet) internal pure returns (InboundPacket memory packet) {\\n packet.origin = Origin(_packet.srcEid(), _packet.sender(), _packet.nonce());\\n packet.dstEid = _packet.dstEid();\\n packet.receiver = _packet.receiverB20();\\n packet.guid = _packet.guid();\\n packet.message = _packet.message();\\n }\\n\\n /**\\n * @dev Decode multiple inbound packets from the given packet data and associated message values.\\n * @param _packets An array of packet data to decode.\\n * @param _packetMsgValues An array of associated message values for each packet.\\n * @return packets An array of InboundPacket structs representing the decoded packets.\\n */\\n function decode(\\n bytes[] calldata _packets,\\n uint256[] memory _packetMsgValues\\n ) internal pure returns (InboundPacket[] memory packets) {\\n packets = new InboundPacket[](_packets.length);\\n for (uint256 i = 0; i < _packets.length; i++) {\\n bytes calldata packet = _packets[i];\\n packets[i] = PacketDecoder.decode(packet);\\n // @dev Allows the verifier to specify the msg.value that gets passed in lzReceive.\\n packets[i].value = _packetMsgValues[i];\\n }\\n }\\n}\\n\",\"keccak256\":\"0xcb2fb1c5b2eb3731de78b479b9c2ab3bba326fe0b0b3a008590f18e881e457a6\",\"license\":\"MIT\"},\"@layerzerolabs/oft-evm/contracts/OFT.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { ERC20 } from \\\"@openzeppelin/contracts/token/ERC20/ERC20.sol\\\";\\nimport { IOFT, OFTCore } from \\\"./OFTCore.sol\\\";\\n\\n/**\\n * @title OFT Contract\\n * @dev OFT is an ERC-20 token that extends the functionality of the OFTCore contract.\\n */\\nabstract contract OFT is OFTCore, ERC20 {\\n /**\\n * @dev Constructor for the OFT contract.\\n * @param _name The name of the OFT.\\n * @param _symbol The symbol of the OFT.\\n * @param _lzEndpoint The LayerZero endpoint address.\\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\\n */\\n constructor(\\n string memory _name,\\n string memory _symbol,\\n address _lzEndpoint,\\n address _delegate\\n ) ERC20(_name, _symbol) OFTCore(decimals(), _lzEndpoint, _delegate) {}\\n\\n /**\\n * @dev Retrieves the address of the underlying ERC20 implementation.\\n * @return The address of the OFT token.\\n *\\n * @dev In the case of OFT, address(this) and erc20 are the same contract.\\n */\\n function token() public view returns (address) {\\n return address(this);\\n }\\n\\n /**\\n * @notice Indicates whether the OFT contract requires approval of the 'token()' to send.\\n * @return requiresApproval Needs approval of the underlying token implementation.\\n *\\n * @dev In the case of OFT where the contract IS the token, approval is NOT required.\\n */\\n function approvalRequired() external pure virtual returns (bool) {\\n return false;\\n }\\n\\n /**\\n * @dev Burns tokens from the sender's specified balance.\\n * @param _from The address to debit the tokens from.\\n * @param _amountLD The amount of tokens to send in local decimals.\\n * @param _minAmountLD The minimum amount to send in local decimals.\\n * @param _dstEid The destination chain ID.\\n * @return amountSentLD The amount sent in local decimals.\\n * @return amountReceivedLD The amount received in local decimals on the remote.\\n */\\n function _debit(\\n address _from,\\n uint256 _amountLD,\\n uint256 _minAmountLD,\\n uint32 _dstEid\\n ) internal virtual override returns (uint256 amountSentLD, uint256 amountReceivedLD) {\\n (amountSentLD, amountReceivedLD) = _debitView(_amountLD, _minAmountLD, _dstEid);\\n\\n // @dev In NON-default OFT, amountSentLD could be 100, with a 10% fee, the amountReceivedLD amount is 90,\\n // therefore amountSentLD CAN differ from amountReceivedLD.\\n\\n // @dev Default OFT burns on src.\\n _burn(_from, amountSentLD);\\n }\\n\\n /**\\n * @dev Credits tokens to the specified address.\\n * @param _to The address to credit the tokens to.\\n * @param _amountLD The amount of tokens to credit in local decimals.\\n * @dev _srcEid The source chain ID.\\n * @return amountReceivedLD The amount of tokens ACTUALLY received in local decimals.\\n */\\n function _credit(\\n address _to,\\n uint256 _amountLD,\\n uint32 /*_srcEid*/\\n ) internal virtual override returns (uint256 amountReceivedLD) {\\n if (_to == address(0x0)) _to = address(0xdead); // _mint(...) does not support address(0x0)\\n // @dev Default OFT mints on dst.\\n _mint(_to, _amountLD);\\n // @dev In the case of NON-default OFT, the _amountLD MIGHT not be == amountReceivedLD.\\n return _amountLD;\\n }\\n}\\n\",\"keccak256\":\"0xdc3582e4a20e02a79050c17058a1f1f42a4335d1a70be06c0a52a3fb05d4c89a\",\"license\":\"MIT\"},\"@layerzerolabs/oft-evm/contracts/OFTCore.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { IERC20 } from \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\n\\nimport { OApp, Origin } from \\\"@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol\\\";\\nimport { OAppOptionsType3 } from \\\"@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol\\\";\\nimport { IOAppMsgInspector } from \\\"@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppMsgInspector.sol\\\";\\n\\nimport { OAppPreCrimeSimulator } from \\\"@layerzerolabs/oapp-evm/contracts/precrime/OAppPreCrimeSimulator.sol\\\";\\n\\nimport { IOFT, SendParam, OFTLimit, OFTReceipt, OFTFeeDetail, MessagingReceipt, MessagingFee } from \\\"./interfaces/IOFT.sol\\\";\\nimport { OFTMsgCodec } from \\\"./libs/OFTMsgCodec.sol\\\";\\nimport { OFTComposeMsgCodec } from \\\"./libs/OFTComposeMsgCodec.sol\\\";\\n\\n/**\\n * @title OFTCore\\n * @dev Abstract contract for the OftChain (OFT) token.\\n */\\nabstract contract OFTCore is IOFT, OApp, OAppPreCrimeSimulator, OAppOptionsType3 {\\n using OFTMsgCodec for bytes;\\n using OFTMsgCodec for bytes32;\\n\\n // @notice Provides a conversion rate when swapping between denominations of SD and LD\\n // - shareDecimals == SD == shared Decimals\\n // - localDecimals == LD == local decimals\\n // @dev Considers that tokens have different decimal amounts on various chains.\\n // @dev eg.\\n // For a token\\n // - locally with 4 decimals --> 1.2345 => uint(12345)\\n // - remotely with 2 decimals --> 1.23 => uint(123)\\n // - The conversion rate would be 10 ** (4 - 2) = 100\\n // @dev If you want to send 1.2345 -> (uint 12345), you CANNOT represent that value on the remote,\\n // you can only display 1.23 -> uint(123).\\n // @dev To preserve the dust that would otherwise be lost on that conversion,\\n // we need to unify a denomination that can be represented on ALL chains inside of the OFT mesh\\n uint256 public immutable decimalConversionRate;\\n\\n // @notice Msg types that are used to identify the various OFT operations.\\n // @dev This can be extended in child contracts for non-default oft operations\\n // @dev These values are used in things like combineOptions() in OAppOptionsType3.sol.\\n uint16 public constant SEND = 1;\\n uint16 public constant SEND_AND_CALL = 2;\\n\\n // Address of an optional contract to inspect both 'message' and 'options'\\n address public msgInspector;\\n event MsgInspectorSet(address inspector);\\n\\n /**\\n * @dev Constructor.\\n * @param _localDecimals The decimals of the token on the local chain (this chain).\\n * @param _endpoint The address of the LayerZero endpoint.\\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\\n */\\n constructor(uint8 _localDecimals, address _endpoint, address _delegate) OApp(_endpoint, _delegate) {\\n if (_localDecimals < sharedDecimals()) revert InvalidLocalDecimals();\\n decimalConversionRate = 10 ** (_localDecimals - sharedDecimals());\\n }\\n\\n /**\\n * @notice Retrieves interfaceID and the version of the OFT.\\n * @return interfaceId The interface ID.\\n * @return version The version.\\n *\\n * @dev interfaceId: This specific interface ID is '0x02e49c2c'.\\n * @dev version: Indicates a cross-chain compatible msg encoding with other OFTs.\\n * @dev If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented.\\n * ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)\\n */\\n function oftVersion() external pure virtual returns (bytes4 interfaceId, uint64 version) {\\n return (type(IOFT).interfaceId, 1);\\n }\\n\\n /**\\n * @dev Retrieves the shared decimals of the OFT.\\n * @return The shared decimals of the OFT.\\n *\\n * @dev Sets an implicit cap on the amount of tokens, over uint64.max() will need some sort of outbound cap / totalSupply cap\\n * Lowest common decimal denominator between chains.\\n * Defaults to 6 decimal places to provide up to 18,446,744,073,709.551615 units (max uint64).\\n * For tokens exceeding this totalSupply(), they will need to override the sharedDecimals function with something smaller.\\n * ie. 4 sharedDecimals would be 1,844,674,407,370,955.1615\\n */\\n function sharedDecimals() public view virtual returns (uint8) {\\n return 6;\\n }\\n\\n /**\\n * @dev Sets the message inspector address for the OFT.\\n * @param _msgInspector The address of the message inspector.\\n *\\n * @dev This is an optional contract that can be used to inspect both 'message' and 'options'.\\n * @dev Set it to address(0) to disable it, or set it to a contract address to enable it.\\n */\\n function setMsgInspector(address _msgInspector) public virtual onlyOwner {\\n msgInspector = _msgInspector;\\n emit MsgInspectorSet(_msgInspector);\\n }\\n\\n /**\\n * @notice Provides the fee breakdown and settings data for an OFT. Unused in the default implementation.\\n * @param _sendParam The parameters for the send operation.\\n * @return oftLimit The OFT limit information.\\n * @return oftFeeDetails The details of OFT fees.\\n * @return oftReceipt The OFT receipt information.\\n */\\n function quoteOFT(\\n SendParam calldata _sendParam\\n )\\n external\\n view\\n virtual\\n returns (OFTLimit memory oftLimit, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory oftReceipt)\\n {\\n uint256 minAmountLD = 0; // Unused in the default implementation.\\n uint256 maxAmountLD = IERC20(this.token()).totalSupply(); // Unused in the default implementation.\\n oftLimit = OFTLimit(minAmountLD, maxAmountLD);\\n\\n // Unused in the default implementation; reserved for future complex fee details.\\n oftFeeDetails = new OFTFeeDetail[](0);\\n\\n // @dev This is the same as the send() operation, but without the actual send.\\n // - amountSentLD is the amount in local decimals that would be sent from the sender.\\n // - amountReceivedLD is the amount in local decimals that will be credited to the recipient on the remote OFT instance.\\n // @dev The amountSentLD MIGHT not equal the amount the user actually receives. HOWEVER, the default does.\\n (uint256 amountSentLD, uint256 amountReceivedLD) = _debitView(\\n _sendParam.amountLD,\\n _sendParam.minAmountLD,\\n _sendParam.dstEid\\n );\\n oftReceipt = OFTReceipt(amountSentLD, amountReceivedLD);\\n }\\n\\n /**\\n * @notice Provides a quote for the send() operation.\\n * @param _sendParam The parameters for the send() operation.\\n * @param _payInLzToken Flag indicating whether the caller is paying in the LZ token.\\n * @return msgFee The calculated LayerZero messaging fee from the send() operation.\\n *\\n * @dev MessagingFee: LayerZero msg fee\\n * - nativeFee: The native fee.\\n * - lzTokenFee: The lzToken fee.\\n */\\n function quoteSend(\\n SendParam calldata _sendParam,\\n bool _payInLzToken\\n ) external view virtual returns (MessagingFee memory msgFee) {\\n // @dev mock the amount to receive, this is the same operation used in the send().\\n // The quote is as similar as possible to the actual send() operation.\\n (, uint256 amountReceivedLD) = _debitView(_sendParam.amountLD, _sendParam.minAmountLD, _sendParam.dstEid);\\n\\n // @dev Builds the options and OFT message to quote in the endpoint.\\n (bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam, amountReceivedLD);\\n\\n // @dev Calculates the LayerZero fee for the send() operation.\\n return _quote(_sendParam.dstEid, message, options, _payInLzToken);\\n }\\n\\n /**\\n * @dev Executes the send operation.\\n * @param _sendParam The parameters for the send operation.\\n * @param _fee The calculated fee for the send() operation.\\n * - nativeFee: The native fee.\\n * - lzTokenFee: The lzToken fee.\\n * @param _refundAddress The address to receive any excess funds.\\n * @return msgReceipt The receipt for the send operation.\\n * @return oftReceipt The OFT receipt information.\\n *\\n * @dev MessagingReceipt: LayerZero msg receipt\\n * - guid: The unique identifier for the sent message.\\n * - nonce: The nonce of the sent message.\\n * - fee: The LayerZero fee incurred for the message.\\n */\\n function send(\\n SendParam calldata _sendParam,\\n MessagingFee calldata _fee,\\n address _refundAddress\\n ) external payable virtual returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) {\\n return _send(_sendParam, _fee, _refundAddress);\\n }\\n\\n /**\\n * @dev Internal function to execute the send operation.\\n * @param _sendParam The parameters for the send operation.\\n * @param _fee The calculated fee for the send() operation.\\n * - nativeFee: The native fee.\\n * - lzTokenFee: The lzToken fee.\\n * @param _refundAddress The address to receive any excess funds.\\n * @return msgReceipt The receipt for the send operation.\\n * @return oftReceipt The OFT receipt information.\\n *\\n * @dev MessagingReceipt: LayerZero msg receipt\\n * - guid: The unique identifier for the sent message.\\n * - nonce: The nonce of the sent message.\\n * - fee: The LayerZero fee incurred for the message.\\n */\\n function _send(\\n SendParam calldata _sendParam,\\n MessagingFee calldata _fee,\\n address _refundAddress\\n ) internal virtual returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) {\\n // @dev Applies the token transfers regarding this send() operation.\\n // - amountSentLD is the amount in local decimals that was ACTUALLY sent/debited from the sender.\\n // - amountReceivedLD is the amount in local decimals that will be received/credited to the recipient on the remote OFT instance.\\n (uint256 amountSentLD, uint256 amountReceivedLD) = _debit(\\n msg.sender,\\n _sendParam.amountLD,\\n _sendParam.minAmountLD,\\n _sendParam.dstEid\\n );\\n\\n // @dev Builds the options and OFT message to quote in the endpoint.\\n (bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam, amountReceivedLD);\\n\\n // @dev Sends the message to the LayerZero endpoint and returns the LayerZero msg receipt.\\n msgReceipt = _lzSend(_sendParam.dstEid, message, options, _fee, _refundAddress);\\n // @dev Formulate the OFT receipt.\\n oftReceipt = OFTReceipt(amountSentLD, amountReceivedLD);\\n\\n emit OFTSent(msgReceipt.guid, _sendParam.dstEid, msg.sender, amountSentLD, amountReceivedLD);\\n }\\n\\n /**\\n * @dev Internal function to build the message and options.\\n * @param _sendParam The parameters for the send() operation.\\n * @param _amountLD The amount in local decimals.\\n * @return message The encoded message.\\n * @return options The encoded options.\\n */\\n function _buildMsgAndOptions(\\n SendParam calldata _sendParam,\\n uint256 _amountLD\\n ) internal view virtual returns (bytes memory message, bytes memory options) {\\n bool hasCompose;\\n // @dev This generated message has the msg.sender encoded into the payload so the remote knows who the caller is.\\n (message, hasCompose) = OFTMsgCodec.encode(\\n _sendParam.to,\\n _toSD(_amountLD),\\n // @dev Must be include a non empty bytes if you want to compose, EVEN if you dont need it on the remote.\\n // EVEN if you dont require an arbitrary payload to be sent... eg. '0x01'\\n _sendParam.composeMsg\\n );\\n // @dev Change the msg type depending if its composed or not.\\n uint16 msgType = hasCompose ? SEND_AND_CALL : SEND;\\n // @dev Combine the callers _extraOptions with the enforced options via the OAppOptionsType3.\\n options = combineOptions(_sendParam.dstEid, msgType, _sendParam.extraOptions);\\n\\n // @dev Optionally inspect the message and options depending if the OApp owner has set a msg inspector.\\n // @dev If it fails inspection, needs to revert in the implementation. ie. does not rely on return boolean\\n address inspector = msgInspector; // caches the msgInspector to avoid potential double storage read\\n if (inspector != address(0)) IOAppMsgInspector(inspector).inspect(message, options);\\n }\\n\\n /**\\n * @dev Internal function to handle the receive on the LayerZero endpoint.\\n * @param _origin The origin information.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address from the src chain.\\n * - nonce: The nonce of the LayerZero message.\\n * @param _guid The unique identifier for the received LayerZero message.\\n * @param _message The encoded message.\\n * @dev _executor The address of the executor.\\n * @dev _extraData Additional data.\\n */\\n function _lzReceive(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address /*_executor*/, // @dev unused in the default implementation.\\n bytes calldata /*_extraData*/ // @dev unused in the default implementation.\\n ) internal virtual override {\\n // @dev The src sending chain doesnt know the address length on this chain (potentially non-evm)\\n // Thus everything is bytes32() encoded in flight.\\n address toAddress = _message.sendTo().bytes32ToAddress();\\n // @dev Credit the amountLD to the recipient and return the ACTUAL amount the recipient received in local decimals\\n uint256 amountReceivedLD = _credit(toAddress, _toLD(_message.amountSD()), _origin.srcEid);\\n\\n if (_message.isComposed()) {\\n // @dev Proprietary composeMsg format for the OFT.\\n bytes memory composeMsg = OFTComposeMsgCodec.encode(\\n _origin.nonce,\\n _origin.srcEid,\\n amountReceivedLD,\\n _message.composeMsg()\\n );\\n\\n // @dev Stores the lzCompose payload that will be executed in a separate tx.\\n // Standardizes functionality for executing arbitrary contract invocation on some non-evm chains.\\n // @dev The off-chain executor will listen and process the msg based on the src-chain-callers compose options passed.\\n // @dev The index is used when a OApp needs to compose multiple msgs on lzReceive.\\n // For default OFT implementation there is only 1 compose msg per lzReceive, thus its always 0.\\n endpoint.sendCompose(toAddress, _guid, 0 /* the index of the composed message*/, composeMsg);\\n }\\n\\n emit OFTReceived(_guid, _origin.srcEid, toAddress, amountReceivedLD);\\n }\\n\\n /**\\n * @dev Internal function to handle the OAppPreCrimeSimulator simulated receive.\\n * @param _origin The origin information.\\n * - srcEid: The source chain endpoint ID.\\n * - sender: The sender address from the src chain.\\n * - nonce: The nonce of the LayerZero message.\\n * @param _guid The unique identifier for the received LayerZero message.\\n * @param _message The LayerZero message.\\n * @param _executor The address of the off-chain executor.\\n * @param _extraData Arbitrary data passed by the msg executor.\\n *\\n * @dev Enables the preCrime simulator to mock sending lzReceive() messages,\\n * routes the msg down from the OAppPreCrimeSimulator, and back up to the OAppReceiver.\\n */\\n function _lzReceiveSimulate(\\n Origin calldata _origin,\\n bytes32 _guid,\\n bytes calldata _message,\\n address _executor,\\n bytes calldata _extraData\\n ) internal virtual override {\\n _lzReceive(_origin, _guid, _message, _executor, _extraData);\\n }\\n\\n /**\\n * @dev Check if the peer is considered 'trusted' by the OApp.\\n * @param _eid The endpoint ID to check.\\n * @param _peer The peer to check.\\n * @return Whether the peer passed is considered 'trusted' by the OApp.\\n *\\n * @dev Enables OAppPreCrimeSimulator to check whether a potential Inbound Packet is from a trusted source.\\n */\\n function isPeer(uint32 _eid, bytes32 _peer) public view virtual override returns (bool) {\\n return peers[_eid] == _peer;\\n }\\n\\n /**\\n * @dev Internal function to remove dust from the given local decimal amount.\\n * @param _amountLD The amount in local decimals.\\n * @return amountLD The amount after removing dust.\\n *\\n * @dev Prevents the loss of dust when moving amounts between chains with different decimals.\\n * @dev eg. uint(123) with a conversion rate of 100 becomes uint(100).\\n */\\n function _removeDust(uint256 _amountLD) internal view virtual returns (uint256 amountLD) {\\n return (_amountLD / decimalConversionRate) * decimalConversionRate;\\n }\\n\\n /**\\n * @dev Internal function to convert an amount from shared decimals into local decimals.\\n * @param _amountSD The amount in shared decimals.\\n * @return amountLD The amount in local decimals.\\n */\\n function _toLD(uint64 _amountSD) internal view virtual returns (uint256 amountLD) {\\n return _amountSD * decimalConversionRate;\\n }\\n\\n /**\\n * @dev Internal function to convert an amount from local decimals into shared decimals.\\n * @param _amountLD The amount in local decimals.\\n * @return amountSD The amount in shared decimals.\\n *\\n * @dev Reverts if the _amountLD in shared decimals overflows uint64.\\n * @dev eg. uint(2**64 + 123) with a conversion rate of 1 wraps around 2**64 to uint(123).\\n */\\n function _toSD(uint256 _amountLD) internal view virtual returns (uint64 amountSD) {\\n uint256 _amountSD = _amountLD / decimalConversionRate;\\n if (_amountSD > type(uint64).max) revert AmountSDOverflowed(_amountSD);\\n return uint64(_amountSD);\\n }\\n\\n /**\\n * @dev Internal function to mock the amount mutation from a OFT debit() operation.\\n * @param _amountLD The amount to send in local decimals.\\n * @param _minAmountLD The minimum amount to send in local decimals.\\n * @dev _dstEid The destination endpoint ID.\\n * @return amountSentLD The amount sent, in local decimals.\\n * @return amountReceivedLD The amount to be received on the remote chain, in local decimals.\\n *\\n * @dev This is where things like fees would be calculated and deducted from the amount to be received on the remote.\\n */\\n function _debitView(\\n uint256 _amountLD,\\n uint256 _minAmountLD,\\n uint32 /*_dstEid*/\\n ) internal view virtual returns (uint256 amountSentLD, uint256 amountReceivedLD) {\\n // @dev Remove the dust so nothing is lost on the conversion between chains with different decimals for the token.\\n amountSentLD = _removeDust(_amountLD);\\n // @dev The amount to send is the same as amount received in the default implementation.\\n amountReceivedLD = amountSentLD;\\n\\n // @dev Check for slippage.\\n if (amountReceivedLD < _minAmountLD) {\\n revert SlippageExceeded(amountReceivedLD, _minAmountLD);\\n }\\n }\\n\\n /**\\n * @dev Internal function to perform a debit operation.\\n * @param _from The address to debit.\\n * @param _amountLD The amount to send in local decimals.\\n * @param _minAmountLD The minimum amount to send in local decimals.\\n * @param _dstEid The destination endpoint ID.\\n * @return amountSentLD The amount sent in local decimals.\\n * @return amountReceivedLD The amount received in local decimals on the remote.\\n *\\n * @dev Defined here but are intended to be overriden depending on the OFT implementation.\\n * @dev Depending on OFT implementation the _amountLD could differ from the amountReceivedLD.\\n */\\n function _debit(\\n address _from,\\n uint256 _amountLD,\\n uint256 _minAmountLD,\\n uint32 _dstEid\\n ) internal virtual returns (uint256 amountSentLD, uint256 amountReceivedLD);\\n\\n /**\\n * @dev Internal function to perform a credit operation.\\n * @param _to The address to credit.\\n * @param _amountLD The amount to credit in local decimals.\\n * @param _srcEid The source endpoint ID.\\n * @return amountReceivedLD The amount ACTUALLY received in local decimals.\\n *\\n * @dev Defined here but are intended to be overriden depending on the OFT implementation.\\n * @dev Depending on OFT implementation the _amountLD could differ from the amountReceivedLD.\\n */\\n function _credit(\\n address _to,\\n uint256 _amountLD,\\n uint32 _srcEid\\n ) internal virtual returns (uint256 amountReceivedLD);\\n}\\n\",\"keccak256\":\"0xdda89798c66928bba9e0fa44b3edf4710ff15cf46edadcf3e15c92d78fcc9ca8\",\"license\":\"MIT\"},\"@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nimport { MessagingReceipt, MessagingFee } from \\\"@layerzerolabs/oapp-evm/contracts/oapp/OAppSender.sol\\\";\\n\\n/**\\n * @dev Struct representing token parameters for the OFT send() operation.\\n */\\nstruct SendParam {\\n uint32 dstEid; // Destination endpoint ID.\\n bytes32 to; // Recipient address.\\n uint256 amountLD; // Amount to send in local decimals.\\n uint256 minAmountLD; // Minimum amount to send in local decimals.\\n bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message.\\n bytes composeMsg; // The composed message for the send() operation.\\n bytes oftCmd; // The OFT command to be executed, unused in default OFT implementations.\\n}\\n\\n/**\\n * @dev Struct representing OFT limit information.\\n * @dev These amounts can change dynamically and are up the specific oft implementation.\\n */\\nstruct OFTLimit {\\n uint256 minAmountLD; // Minimum amount in local decimals that can be sent to the recipient.\\n uint256 maxAmountLD; // Maximum amount in local decimals that can be sent to the recipient.\\n}\\n\\n/**\\n * @dev Struct representing OFT receipt information.\\n */\\nstruct OFTReceipt {\\n uint256 amountSentLD; // Amount of tokens ACTUALLY debited from the sender in local decimals.\\n // @dev In non-default implementations, the amountReceivedLD COULD differ from this value.\\n uint256 amountReceivedLD; // Amount of tokens to be received on the remote side.\\n}\\n\\n/**\\n * @dev Struct representing OFT fee details.\\n * @dev Future proof mechanism to provide a standardized way to communicate fees to things like a UI.\\n */\\nstruct OFTFeeDetail {\\n int256 feeAmountLD; // Amount of the fee in local decimals.\\n string description; // Description of the fee.\\n}\\n\\n/**\\n * @title IOFT\\n * @dev Interface for the OftChain (OFT) token.\\n * @dev Does not inherit ERC20 to accommodate usage by OFTAdapter as well.\\n * @dev This specific interface ID is '0x02e49c2c'.\\n */\\ninterface IOFT {\\n // Custom error messages\\n error InvalidLocalDecimals();\\n error SlippageExceeded(uint256 amountLD, uint256 minAmountLD);\\n error AmountSDOverflowed(uint256 amountSD);\\n\\n // Events\\n event OFTSent(\\n bytes32 indexed guid, // GUID of the OFT message.\\n uint32 dstEid, // Destination Endpoint ID.\\n address indexed fromAddress, // Address of the sender on the src chain.\\n uint256 amountSentLD, // Amount of tokens sent in local decimals.\\n uint256 amountReceivedLD // Amount of tokens received in local decimals.\\n );\\n event OFTReceived(\\n bytes32 indexed guid, // GUID of the OFT message.\\n uint32 srcEid, // Source Endpoint ID.\\n address indexed toAddress, // Address of the recipient on the dst chain.\\n uint256 amountReceivedLD // Amount of tokens received in local decimals.\\n );\\n\\n /**\\n * @notice Retrieves interfaceID and the version of the OFT.\\n * @return interfaceId The interface ID.\\n * @return version The version.\\n *\\n * @dev interfaceId: This specific interface ID is '0x02e49c2c'.\\n * @dev version: Indicates a cross-chain compatible msg encoding with other OFTs.\\n * @dev If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented.\\n * ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)\\n */\\n function oftVersion() external view returns (bytes4 interfaceId, uint64 version);\\n\\n /**\\n * @notice Retrieves the address of the token associated with the OFT.\\n * @return token The address of the ERC20 token implementation.\\n */\\n function token() external view returns (address);\\n\\n /**\\n * @notice Indicates whether the OFT contract requires approval of the 'token()' to send.\\n * @return requiresApproval Needs approval of the underlying token implementation.\\n *\\n * @dev Allows things like wallet implementers to determine integration requirements,\\n * without understanding the underlying token implementation.\\n */\\n function approvalRequired() external view returns (bool);\\n\\n /**\\n * @notice Retrieves the shared decimals of the OFT.\\n * @return sharedDecimals The shared decimals of the OFT.\\n */\\n function sharedDecimals() external view returns (uint8);\\n\\n /**\\n * @notice Provides the fee breakdown and settings data for an OFT. Unused in the default implementation.\\n * @param _sendParam The parameters for the send operation.\\n * @return limit The OFT limit information.\\n * @return oftFeeDetails The details of OFT fees.\\n * @return receipt The OFT receipt information.\\n */\\n function quoteOFT(\\n SendParam calldata _sendParam\\n ) external view returns (OFTLimit memory, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory);\\n\\n /**\\n * @notice Provides a quote for the send() operation.\\n * @param _sendParam The parameters for the send() operation.\\n * @param _payInLzToken Flag indicating whether the caller is paying in the LZ token.\\n * @return fee The calculated LayerZero messaging fee from the send() operation.\\n *\\n * @dev MessagingFee: LayerZero msg fee\\n * - nativeFee: The native fee.\\n * - lzTokenFee: The lzToken fee.\\n */\\n function quoteSend(SendParam calldata _sendParam, bool _payInLzToken) external view returns (MessagingFee memory);\\n\\n /**\\n * @notice Executes the send() operation.\\n * @param _sendParam The parameters for the send operation.\\n * @param _fee The fee information supplied by the caller.\\n * - nativeFee: The native fee.\\n * - lzTokenFee: The lzToken fee.\\n * @param _refundAddress The address to receive any excess funds from fees etc. on the src.\\n * @return receipt The LayerZero messaging receipt from the send() operation.\\n * @return oftReceipt The OFT receipt information.\\n *\\n * @dev MessagingReceipt: LayerZero msg receipt\\n * - guid: The unique identifier for the sent message.\\n * - nonce: The nonce of the sent message.\\n * - fee: The LayerZero fee incurred for the message.\\n */\\n function send(\\n SendParam calldata _sendParam,\\n MessagingFee calldata _fee,\\n address _refundAddress\\n ) external payable returns (MessagingReceipt memory, OFTReceipt memory);\\n}\\n\",\"keccak256\":\"0xc60c7b4374b3d89f33b8de982f463c92374a8548800c816fe776f0ec76351fb0\",\"license\":\"MIT\"},\"@layerzerolabs/oft-evm/contracts/libs/OFTComposeMsgCodec.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nlibrary OFTComposeMsgCodec {\\n // Offset constants for decoding composed messages\\n uint8 private constant NONCE_OFFSET = 8;\\n uint8 private constant SRC_EID_OFFSET = 12;\\n uint8 private constant AMOUNT_LD_OFFSET = 44;\\n uint8 private constant COMPOSE_FROM_OFFSET = 76;\\n\\n /**\\n * @dev Encodes a OFT composed message.\\n * @param _nonce The nonce value.\\n * @param _srcEid The source endpoint ID.\\n * @param _amountLD The amount in local decimals.\\n * @param _composeMsg The composed message.\\n * @return _msg The encoded Composed message.\\n */\\n function encode(\\n uint64 _nonce,\\n uint32 _srcEid,\\n uint256 _amountLD,\\n bytes memory _composeMsg // 0x[composeFrom][composeMsg]\\n ) internal pure returns (bytes memory _msg) {\\n _msg = abi.encodePacked(_nonce, _srcEid, _amountLD, _composeMsg);\\n }\\n\\n /**\\n * @dev Retrieves the nonce for the composed message.\\n * @param _msg The message.\\n * @return The nonce value.\\n */\\n function nonce(bytes calldata _msg) internal pure returns (uint64) {\\n return uint64(bytes8(_msg[:NONCE_OFFSET]));\\n }\\n\\n /**\\n * @dev Retrieves the source endpoint ID for the composed message.\\n * @param _msg The message.\\n * @return The source endpoint ID.\\n */\\n function srcEid(bytes calldata _msg) internal pure returns (uint32) {\\n return uint32(bytes4(_msg[NONCE_OFFSET:SRC_EID_OFFSET]));\\n }\\n\\n /**\\n * @dev Retrieves the amount in local decimals from the composed message.\\n * @param _msg The message.\\n * @return The amount in local decimals.\\n */\\n function amountLD(bytes calldata _msg) internal pure returns (uint256) {\\n return uint256(bytes32(_msg[SRC_EID_OFFSET:AMOUNT_LD_OFFSET]));\\n }\\n\\n /**\\n * @dev Retrieves the composeFrom value from the composed message.\\n * @param _msg The message.\\n * @return The composeFrom value.\\n */\\n function composeFrom(bytes calldata _msg) internal pure returns (bytes32) {\\n return bytes32(_msg[AMOUNT_LD_OFFSET:COMPOSE_FROM_OFFSET]);\\n }\\n\\n /**\\n * @dev Retrieves the composed message.\\n * @param _msg The message.\\n * @return The composed message.\\n */\\n function composeMsg(bytes calldata _msg) internal pure returns (bytes memory) {\\n return _msg[COMPOSE_FROM_OFFSET:];\\n }\\n\\n /**\\n * @dev Converts an address to bytes32.\\n * @param _addr The address to convert.\\n * @return The bytes32 representation of the address.\\n */\\n function addressToBytes32(address _addr) internal pure returns (bytes32) {\\n return bytes32(uint256(uint160(_addr)));\\n }\\n\\n /**\\n * @dev Converts bytes32 to an address.\\n * @param _b The bytes32 value to convert.\\n * @return The address representation of bytes32.\\n */\\n function bytes32ToAddress(bytes32 _b) internal pure returns (address) {\\n return address(uint160(uint256(_b)));\\n }\\n}\\n\",\"keccak256\":\"0xaae73d6eb8b9561c43f1802f3c416c00ccd35f172b711f9781ccdf1b25a40db5\",\"license\":\"MIT\"},\"@layerzerolabs/oft-evm/contracts/libs/OFTMsgCodec.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.20;\\n\\nlibrary OFTMsgCodec {\\n // Offset constants for encoding and decoding OFT messages\\n uint8 private constant SEND_TO_OFFSET = 32;\\n uint8 private constant SEND_AMOUNT_SD_OFFSET = 40;\\n\\n /**\\n * @dev Encodes an OFT LayerZero message.\\n * @param _sendTo The recipient address.\\n * @param _amountShared The amount in shared decimals.\\n * @param _composeMsg The composed message.\\n * @return _msg The encoded message.\\n * @return hasCompose A boolean indicating whether the message has a composed payload.\\n */\\n function encode(\\n bytes32 _sendTo,\\n uint64 _amountShared,\\n bytes memory _composeMsg\\n ) internal view returns (bytes memory _msg, bool hasCompose) {\\n hasCompose = _composeMsg.length > 0;\\n // @dev Remote chains will want to know the composed function caller ie. msg.sender on the src.\\n _msg = hasCompose\\n ? abi.encodePacked(_sendTo, _amountShared, addressToBytes32(msg.sender), _composeMsg)\\n : abi.encodePacked(_sendTo, _amountShared);\\n }\\n\\n /**\\n * @dev Checks if the OFT message is composed.\\n * @param _msg The OFT message.\\n * @return A boolean indicating whether the message is composed.\\n */\\n function isComposed(bytes calldata _msg) internal pure returns (bool) {\\n return _msg.length > SEND_AMOUNT_SD_OFFSET;\\n }\\n\\n /**\\n * @dev Retrieves the recipient address from the OFT message.\\n * @param _msg The OFT message.\\n * @return The recipient address.\\n */\\n function sendTo(bytes calldata _msg) internal pure returns (bytes32) {\\n return bytes32(_msg[:SEND_TO_OFFSET]);\\n }\\n\\n /**\\n * @dev Retrieves the amount in shared decimals from the OFT message.\\n * @param _msg The OFT message.\\n * @return The amount in shared decimals.\\n */\\n function amountSD(bytes calldata _msg) internal pure returns (uint64) {\\n return uint64(bytes8(_msg[SEND_TO_OFFSET:SEND_AMOUNT_SD_OFFSET]));\\n }\\n\\n /**\\n * @dev Retrieves the composed message from the OFT message.\\n * @param _msg The OFT message.\\n * @return The composed message.\\n */\\n function composeMsg(bytes calldata _msg) internal pure returns (bytes memory) {\\n return _msg[SEND_AMOUNT_SD_OFFSET:];\\n }\\n\\n /**\\n * @dev Converts an address to bytes32.\\n * @param _addr The address to convert.\\n * @return The bytes32 representation of the address.\\n */\\n function addressToBytes32(address _addr) internal pure returns (bytes32) {\\n return bytes32(uint256(uint160(_addr)));\\n }\\n\\n /**\\n * @dev Converts bytes32 to an address.\\n * @param _b The bytes32 value to convert.\\n * @return The address representation of bytes32.\\n */\\n function bytes32ToAddress(bytes32 _b) internal pure returns (address) {\\n return address(uint160(uint256(_b)));\\n }\\n}\\n\",\"keccak256\":\"0x5358948017669c03e157f871d8c38e988f9004dbd0801ad3119d2487f0d40b0b\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/Ownable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\\n\\npragma solidity ^0.8.20;\\n\\nimport {Context} from \\\"../utils/Context.sol\\\";\\n\\n/**\\n * @dev Contract module which provides a basic access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * The initial owner is set to the address provided by the deployer. This can\\n * later be changed with {transferOwnership}.\\n *\\n * This module is used through inheritance. It will make available the modifier\\n * `onlyOwner`, which can be applied to your functions to restrict their use to\\n * the owner.\\n */\\nabstract contract Ownable is Context {\\n address private _owner;\\n\\n /**\\n * @dev The caller account is not authorized to perform an operation.\\n */\\n error OwnableUnauthorizedAccount(address account);\\n\\n /**\\n * @dev The owner is not a valid owner account. (eg. `address(0)`)\\n */\\n error OwnableInvalidOwner(address owner);\\n\\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\\n\\n /**\\n * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\\n */\\n constructor(address initialOwner) {\\n if (initialOwner == address(0)) {\\n revert OwnableInvalidOwner(address(0));\\n }\\n _transferOwnership(initialOwner);\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the owner.\\n */\\n modifier onlyOwner() {\\n _checkOwner();\\n _;\\n }\\n\\n /**\\n * @dev Returns the address of the current owner.\\n */\\n function owner() public view virtual returns (address) {\\n return _owner;\\n }\\n\\n /**\\n * @dev Throws if the sender is not the owner.\\n */\\n function _checkOwner() internal view virtual {\\n if (owner() != _msgSender()) {\\n revert OwnableUnauthorizedAccount(_msgSender());\\n }\\n }\\n\\n /**\\n * @dev Leaves the contract without owner. It will not be possible to call\\n * `onlyOwner` functions. Can only be called by the current owner.\\n *\\n * NOTE: Renouncing ownership will leave the contract without an owner,\\n * thereby disabling any functionality that is only available to the owner.\\n */\\n function renounceOwnership() public virtual onlyOwner {\\n _transferOwnership(address(0));\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual onlyOwner {\\n if (newOwner == address(0)) {\\n revert OwnableInvalidOwner(address(0));\\n }\\n _transferOwnership(newOwner);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Internal function without access restriction.\\n */\\n function _transferOwnership(address newOwner) internal virtual {\\n address oldOwner = _owner;\\n _owner = newOwner;\\n emit OwnershipTransferred(oldOwner, newOwner);\\n }\\n}\\n\",\"keccak256\":\"0xff6d0bb2e285473e5311d9d3caacb525ae3538a80758c10649a4d61029b017bb\",\"license\":\"MIT\"},\"@openzeppelin/contracts/interfaces/IERC1363.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1363.sol)\\n\\npragma solidity >=0.6.2;\\n\\nimport {IERC20} from \\\"./IERC20.sol\\\";\\nimport {IERC165} from \\\"./IERC165.sol\\\";\\n\\n/**\\n * @title IERC1363\\n * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].\\n *\\n * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract\\n * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.\\n */\\ninterface IERC1363 is IERC20, IERC165 {\\n /*\\n * Note: the ERC-165 identifier for this interface is 0xb0202a11.\\n * 0xb0202a11 ===\\n * bytes4(keccak256('transferAndCall(address,uint256)')) ^\\n * bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^\\n * bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^\\n * bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^\\n * bytes4(keccak256('approveAndCall(address,uint256)')) ^\\n * bytes4(keccak256('approveAndCall(address,uint256,bytes)'))\\n */\\n\\n /**\\n * @dev Moves a `value` amount of tokens from the caller's account to `to`\\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\\n * @param to The address which you want to transfer to.\\n * @param value The amount of tokens to be transferred.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function transferAndCall(address to, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from the caller's account to `to`\\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\\n * @param to The address which you want to transfer to.\\n * @param value The amount of tokens to be transferred.\\n * @param data Additional data with no specified format, sent in call to `to`.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism\\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\\n * @param from The address which you want to send tokens from.\\n * @param to The address which you want to transfer to.\\n * @param value The amount of tokens to be transferred.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function transferFromAndCall(address from, address to, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism\\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\\n * @param from The address which you want to send tokens from.\\n * @param to The address which you want to transfer to.\\n * @param value The amount of tokens to be transferred.\\n * @param data Additional data with no specified format, sent in call to `to`.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);\\n\\n /**\\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\\n * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.\\n * @param spender The address which will spend the funds.\\n * @param value The amount of tokens to be spent.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function approveAndCall(address spender, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\\n * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.\\n * @param spender The address which will spend the funds.\\n * @param value The amount of tokens to be spent.\\n * @param data Additional data with no specified format, sent in call to `spender`.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);\\n}\\n\",\"keccak256\":\"0xd5ea07362ab630a6a3dee4285a74cf2377044ca2e4be472755ad64d7c5d4b69d\",\"license\":\"MIT\"},\"@openzeppelin/contracts/interfaces/IERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC165.sol)\\n\\npragma solidity >=0.4.16;\\n\\nimport {IERC165} from \\\"../utils/introspection/IERC165.sol\\\";\\n\",\"keccak256\":\"0x0afcb7e740d1537b252cb2676f600465ce6938398569f09ba1b9ca240dde2dfc\",\"license\":\"MIT\"},\"@openzeppelin/contracts/interfaces/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC20.sol)\\n\\npragma solidity >=0.4.16;\\n\\nimport {IERC20} from \\\"../token/ERC20/IERC20.sol\\\";\\n\",\"keccak256\":\"0x1a6221315ce0307746c2c4827c125d821ee796c74a676787762f4778671d4f44\",\"license\":\"MIT\"},\"@openzeppelin/contracts/interfaces/draft-IERC6093.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.5.0) (interfaces/draft-IERC6093.sol)\\n\\npragma solidity >=0.8.4;\\n\\n/**\\n * @dev Standard ERC-20 Errors\\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-20 tokens.\\n */\\ninterface IERC20Errors {\\n /**\\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n * @param balance Current balance for the interacting account.\\n * @param needed Minimum amount required to perform a transfer.\\n */\\n error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);\\n\\n /**\\n * @dev Indicates a failure with the token `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n */\\n error ERC20InvalidSender(address sender);\\n\\n /**\\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\\n * @param receiver Address to which tokens are being transferred.\\n */\\n error ERC20InvalidReceiver(address receiver);\\n\\n /**\\n * @dev Indicates a failure with the `spender`\\u2019s `allowance`. Used in transfers.\\n * @param spender Address that may be allowed to operate on tokens without being their owner.\\n * @param allowance Amount of tokens a `spender` is allowed to operate with.\\n * @param needed Minimum amount required to perform a transfer.\\n */\\n error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);\\n\\n /**\\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\\n * @param approver Address initiating an approval operation.\\n */\\n error ERC20InvalidApprover(address approver);\\n\\n /**\\n * @dev Indicates a failure with the `spender` to be approved. Used in approvals.\\n * @param spender Address that may be allowed to operate on tokens without being their owner.\\n */\\n error ERC20InvalidSpender(address spender);\\n}\\n\\n/**\\n * @dev Standard ERC-721 Errors\\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-721 tokens.\\n */\\ninterface IERC721Errors {\\n /**\\n * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-721.\\n * Used in balance queries.\\n * @param owner Address of the current owner of a token.\\n */\\n error ERC721InvalidOwner(address owner);\\n\\n /**\\n * @dev Indicates a `tokenId` whose `owner` is the zero address.\\n * @param tokenId Identifier number of a token.\\n */\\n error ERC721NonexistentToken(uint256 tokenId);\\n\\n /**\\n * @dev Indicates an error related to the ownership over a particular token. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n * @param tokenId Identifier number of a token.\\n * @param owner Address of the current owner of a token.\\n */\\n error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);\\n\\n /**\\n * @dev Indicates a failure with the token `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n */\\n error ERC721InvalidSender(address sender);\\n\\n /**\\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\\n * @param receiver Address to which tokens are being transferred.\\n */\\n error ERC721InvalidReceiver(address receiver);\\n\\n /**\\n * @dev Indicates a failure with the `operator`\\u2019s approval. Used in transfers.\\n * @param operator Address that may be allowed to operate on tokens without being their owner.\\n * @param tokenId Identifier number of a token.\\n */\\n error ERC721InsufficientApproval(address operator, uint256 tokenId);\\n\\n /**\\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\\n * @param approver Address initiating an approval operation.\\n */\\n error ERC721InvalidApprover(address approver);\\n\\n /**\\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\\n * @param operator Address that may be allowed to operate on tokens without being their owner.\\n */\\n error ERC721InvalidOperator(address operator);\\n}\\n\\n/**\\n * @dev Standard ERC-1155 Errors\\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-1155 tokens.\\n */\\ninterface IERC1155Errors {\\n /**\\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n * @param balance Current balance for the interacting account.\\n * @param needed Minimum amount required to perform a transfer.\\n * @param tokenId Identifier number of a token.\\n */\\n error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);\\n\\n /**\\n * @dev Indicates a failure with the token `sender`. Used in transfers.\\n * @param sender Address whose tokens are being transferred.\\n */\\n error ERC1155InvalidSender(address sender);\\n\\n /**\\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\\n * @param receiver Address to which tokens are being transferred.\\n */\\n error ERC1155InvalidReceiver(address receiver);\\n\\n /**\\n * @dev Indicates a failure with the `operator`\\u2019s approval. Used in transfers.\\n * @param operator Address that may be allowed to operate on tokens without being their owner.\\n * @param owner Address of the current owner of a token.\\n */\\n error ERC1155MissingApprovalForAll(address operator, address owner);\\n\\n /**\\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\\n * @param approver Address initiating an approval operation.\\n */\\n error ERC1155InvalidApprover(address approver);\\n\\n /**\\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\\n * @param operator Address that may be allowed to operate on tokens without being their owner.\\n */\\n error ERC1155InvalidOperator(address operator);\\n\\n /**\\n * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.\\n * Used in batch transfers.\\n * @param idsLength Length of the array of token identifiers\\n * @param valuesLength Length of the array of token amounts\\n */\\n error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);\\n}\\n\",\"keccak256\":\"0x1b88b3fb3d85ba5496d7d5f396f83ee1fddcdd6762059ff65992655b67920998\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/ERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.5.0) (token/ERC20/ERC20.sol)\\n\\npragma solidity ^0.8.20;\\n\\nimport {IERC20} from \\\"./IERC20.sol\\\";\\nimport {IERC20Metadata} from \\\"./extensions/IERC20Metadata.sol\\\";\\nimport {Context} from \\\"../../utils/Context.sol\\\";\\nimport {IERC20Errors} from \\\"../../interfaces/draft-IERC6093.sol\\\";\\n\\n/**\\n * @dev Implementation of the {IERC20} interface.\\n *\\n * This implementation is agnostic to the way tokens are created. This means\\n * that a supply mechanism has to be added in a derived contract using {_mint}.\\n *\\n * TIP: For a detailed writeup see our guide\\n * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How\\n * to implement supply mechanisms].\\n *\\n * The default value of {decimals} is 18. To change this, you should override\\n * this function so it returns a different value.\\n *\\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\\n * instead returning `false` on failure. This behavior is nonetheless\\n * conventional and does not conflict with the expectations of ERC-20\\n * applications.\\n */\\nabstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {\\n mapping(address account => uint256) private _balances;\\n\\n mapping(address account => mapping(address spender => uint256)) private _allowances;\\n\\n uint256 private _totalSupply;\\n\\n string private _name;\\n string private _symbol;\\n\\n /**\\n * @dev Sets the values for {name} and {symbol}.\\n *\\n * Both values are immutable: they can only be set once during construction.\\n */\\n constructor(string memory name_, string memory symbol_) {\\n _name = name_;\\n _symbol = symbol_;\\n }\\n\\n /**\\n * @dev Returns the name of the token.\\n */\\n function name() public view virtual returns (string memory) {\\n return _name;\\n }\\n\\n /**\\n * @dev Returns the symbol of the token, usually a shorter version of the\\n * name.\\n */\\n function symbol() public view virtual returns (string memory) {\\n return _symbol;\\n }\\n\\n /**\\n * @dev Returns the number of decimals used to get its user representation.\\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\\n *\\n * Tokens usually opt for a value of 18, imitating the relationship between\\n * Ether and Wei. This is the default value returned by this function, unless\\n * it's overridden.\\n *\\n * NOTE: This information is only used for _display_ purposes: it in\\n * no way affects any of the arithmetic of the contract, including\\n * {IERC20-balanceOf} and {IERC20-transfer}.\\n */\\n function decimals() public view virtual returns (uint8) {\\n return 18;\\n }\\n\\n /// @inheritdoc IERC20\\n function totalSupply() public view virtual returns (uint256) {\\n return _totalSupply;\\n }\\n\\n /// @inheritdoc IERC20\\n function balanceOf(address account) public view virtual returns (uint256) {\\n return _balances[account];\\n }\\n\\n /**\\n * @dev See {IERC20-transfer}.\\n *\\n * Requirements:\\n *\\n * - `to` cannot be the zero address.\\n * - the caller must have a balance of at least `value`.\\n */\\n function transfer(address to, uint256 value) public virtual returns (bool) {\\n address owner = _msgSender();\\n _transfer(owner, to, value);\\n return true;\\n }\\n\\n /// @inheritdoc IERC20\\n function allowance(address owner, address spender) public view virtual returns (uint256) {\\n return _allowances[owner][spender];\\n }\\n\\n /**\\n * @dev See {IERC20-approve}.\\n *\\n * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on\\n * `transferFrom`. This is semantically equivalent to an infinite approval.\\n *\\n * Requirements:\\n *\\n * - `spender` cannot be the zero address.\\n */\\n function approve(address spender, uint256 value) public virtual returns (bool) {\\n address owner = _msgSender();\\n _approve(owner, spender, value);\\n return true;\\n }\\n\\n /**\\n * @dev See {IERC20-transferFrom}.\\n *\\n * Skips emitting an {Approval} event indicating an allowance update. This is not\\n * required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].\\n *\\n * NOTE: Does not update the allowance if the current allowance\\n * is the maximum `uint256`.\\n *\\n * Requirements:\\n *\\n * - `from` and `to` cannot be the zero address.\\n * - `from` must have a balance of at least `value`.\\n * - the caller must have allowance for ``from``'s tokens of at least\\n * `value`.\\n */\\n function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {\\n address spender = _msgSender();\\n _spendAllowance(from, spender, value);\\n _transfer(from, to, value);\\n return true;\\n }\\n\\n /**\\n * @dev Moves a `value` amount of tokens from `from` to `to`.\\n *\\n * This internal function is equivalent to {transfer}, and can be used to\\n * e.g. implement automatic token fees, slashing mechanisms, etc.\\n *\\n * Emits a {Transfer} event.\\n *\\n * NOTE: This function is not virtual, {_update} should be overridden instead.\\n */\\n function _transfer(address from, address to, uint256 value) internal {\\n if (from == address(0)) {\\n revert ERC20InvalidSender(address(0));\\n }\\n if (to == address(0)) {\\n revert ERC20InvalidReceiver(address(0));\\n }\\n _update(from, to, value);\\n }\\n\\n /**\\n * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`\\n * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding\\n * this function.\\n *\\n * Emits a {Transfer} event.\\n */\\n function _update(address from, address to, uint256 value) internal virtual {\\n if (from == address(0)) {\\n // Overflow check required: The rest of the code assumes that totalSupply never overflows\\n _totalSupply += value;\\n } else {\\n uint256 fromBalance = _balances[from];\\n if (fromBalance < value) {\\n revert ERC20InsufficientBalance(from, fromBalance, value);\\n }\\n unchecked {\\n // Overflow not possible: value <= fromBalance <= totalSupply.\\n _balances[from] = fromBalance - value;\\n }\\n }\\n\\n if (to == address(0)) {\\n unchecked {\\n // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.\\n _totalSupply -= value;\\n }\\n } else {\\n unchecked {\\n // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.\\n _balances[to] += value;\\n }\\n }\\n\\n emit Transfer(from, to, value);\\n }\\n\\n /**\\n * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).\\n * Relies on the `_update` mechanism\\n *\\n * Emits a {Transfer} event with `from` set to the zero address.\\n *\\n * NOTE: This function is not virtual, {_update} should be overridden instead.\\n */\\n function _mint(address account, uint256 value) internal {\\n if (account == address(0)) {\\n revert ERC20InvalidReceiver(address(0));\\n }\\n _update(address(0), account, value);\\n }\\n\\n /**\\n * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.\\n * Relies on the `_update` mechanism.\\n *\\n * Emits a {Transfer} event with `to` set to the zero address.\\n *\\n * NOTE: This function is not virtual, {_update} should be overridden instead\\n */\\n function _burn(address account, uint256 value) internal {\\n if (account == address(0)) {\\n revert ERC20InvalidSender(address(0));\\n }\\n _update(account, address(0), value);\\n }\\n\\n /**\\n * @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.\\n *\\n * This internal function is equivalent to `approve`, and can be used to\\n * e.g. set automatic allowances for certain subsystems, etc.\\n *\\n * Emits an {Approval} event.\\n *\\n * Requirements:\\n *\\n * - `owner` cannot be the zero address.\\n * - `spender` cannot be the zero address.\\n *\\n * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.\\n */\\n function _approve(address owner, address spender, uint256 value) internal {\\n _approve(owner, spender, value, true);\\n }\\n\\n /**\\n * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.\\n *\\n * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by\\n * `_spendAllowance` during the `transferFrom` operation sets the flag to false. This saves gas by not emitting any\\n * `Approval` event during `transferFrom` operations.\\n *\\n * Anyone who wishes to continue emitting `Approval` events on the `transferFrom` operation can force the flag to\\n * true using the following override:\\n *\\n * ```solidity\\n * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {\\n * super._approve(owner, spender, value, true);\\n * }\\n * ```\\n *\\n * Requirements are the same as {_approve}.\\n */\\n function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {\\n if (owner == address(0)) {\\n revert ERC20InvalidApprover(address(0));\\n }\\n if (spender == address(0)) {\\n revert ERC20InvalidSpender(address(0));\\n }\\n _allowances[owner][spender] = value;\\n if (emitEvent) {\\n emit Approval(owner, spender, value);\\n }\\n }\\n\\n /**\\n * @dev Updates `owner`'s allowance for `spender` based on spent `value`.\\n *\\n * Does not update the allowance value in case of infinite allowance.\\n * Revert if not enough allowance is available.\\n *\\n * Does not emit an {Approval} event.\\n */\\n function _spendAllowance(address owner, address spender, uint256 value) internal virtual {\\n uint256 currentAllowance = allowance(owner, spender);\\n if (currentAllowance < type(uint256).max) {\\n if (currentAllowance < value) {\\n revert ERC20InsufficientAllowance(spender, currentAllowance, value);\\n }\\n unchecked {\\n _approve(owner, spender, currentAllowance - value, false);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x669464167428061ee0f8618b73b3ee90aff8405683e7ddde8cd77dadaa1afe29\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)\\n\\npragma solidity >=0.4.16;\\n\\n/**\\n * @dev Interface of the ERC-20 standard as defined in the ERC.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n\\n /**\\n * @dev Returns the value of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the value of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from the caller's account to `to`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address to, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\\n * caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from `from` to `to` using the\\n * allowance mechanism. `value` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(address from, address to, uint256 value) external returns (bool);\\n}\\n\",\"keccak256\":\"0x74ed01eb66b923d0d0cfe3be84604ac04b76482a55f9dd655e1ef4d367f95bc2\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/extensions/IERC20Metadata.sol)\\n\\npragma solidity >=0.6.2;\\n\\nimport {IERC20} from \\\"../IERC20.sol\\\";\\n\\n/**\\n * @dev Interface for the optional metadata functions from the ERC-20 standard.\\n */\\ninterface IERC20Metadata is IERC20 {\\n /**\\n * @dev Returns the name of the token.\\n */\\n function name() external view returns (string memory);\\n\\n /**\\n * @dev Returns the symbol of the token.\\n */\\n function symbol() external view returns (string memory);\\n\\n /**\\n * @dev Returns the decimals places of the token.\\n */\\n function decimals() external view returns (uint8);\\n}\\n\",\"keccak256\":\"0xd6fa4088198f04eef10c5bce8a2f4d60554b7ec4b987f684393c01bf79b94d9f\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.5.0) (token/ERC20/utils/SafeERC20.sol)\\n\\npragma solidity ^0.8.20;\\n\\nimport {IERC20} from \\\"../IERC20.sol\\\";\\nimport {IERC1363} from \\\"../../../interfaces/IERC1363.sol\\\";\\n\\n/**\\n * @title SafeERC20\\n * @dev Wrappers around ERC-20 operations that throw on failure (when the token\\n * contract returns false). Tokens that return no value (and instead revert or\\n * throw on failure) are also supported, non-reverting calls are assumed to be\\n * successful.\\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\\n */\\nlibrary SafeERC20 {\\n /**\\n * @dev An operation with an ERC-20 token failed.\\n */\\n error SafeERC20FailedOperation(address token);\\n\\n /**\\n * @dev Indicates a failed `decreaseAllowance` request.\\n */\\n error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);\\n\\n /**\\n * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful.\\n */\\n function safeTransfer(IERC20 token, address to, uint256 value) internal {\\n if (!_safeTransfer(token, to, value, true)) {\\n revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the\\n * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.\\n */\\n function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {\\n if (!_safeTransferFrom(token, from, to, value, true)) {\\n revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.\\n */\\n function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {\\n return _safeTransfer(token, to, value, false);\\n }\\n\\n /**\\n * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.\\n */\\n function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {\\n return _safeTransferFrom(token, from, to, value, false);\\n }\\n\\n /**\\n * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful.\\n *\\n * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the \\\"client\\\"\\n * smart contract uses ERC-7674 to set temporary allowances, then the \\\"client\\\" smart contract should avoid using\\n * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract\\n * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.\\n */\\n function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {\\n uint256 oldAllowance = token.allowance(address(this), spender);\\n forceApprove(token, spender, oldAllowance + value);\\n }\\n\\n /**\\n * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no\\n * value, non-reverting calls are assumed to be successful.\\n *\\n * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the \\\"client\\\"\\n * smart contract uses ERC-7674 to set temporary allowances, then the \\\"client\\\" smart contract should avoid using\\n * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract\\n * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.\\n */\\n function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {\\n unchecked {\\n uint256 currentAllowance = token.allowance(address(this), spender);\\n if (currentAllowance < requestedDecrease) {\\n revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);\\n }\\n forceApprove(token, spender, currentAllowance - requestedDecrease);\\n }\\n }\\n\\n /**\\n * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval\\n * to be set to zero before setting it to a non-zero value, such as USDT.\\n *\\n * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function\\n * only sets the \\\"standard\\\" allowance. Any temporary allowance will remain active, in addition to the value being\\n * set here.\\n */\\n function forceApprove(IERC20 token, address spender, uint256 value) internal {\\n if (!_safeApprove(token, spender, value, false)) {\\n if (!_safeApprove(token, spender, 0, true)) revert SafeERC20FailedOperation(address(token));\\n if (!_safeApprove(token, spender, value, true)) revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no\\n * code. This can be used to implement an {ERC721}-like safe transfer that relies on {ERC1363} checks when\\n * targeting contracts.\\n *\\n * Reverts if the returned value is other than `true`.\\n */\\n function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {\\n if (to.code.length == 0) {\\n safeTransfer(token, to, value);\\n } else if (!token.transferAndCall(to, value, data)) {\\n revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target\\n * has no code. This can be used to implement an {ERC721}-like safe transfer that relies on {ERC1363} checks when\\n * targeting contracts.\\n *\\n * Reverts if the returned value is other than `true`.\\n */\\n function transferFromAndCallRelaxed(\\n IERC1363 token,\\n address from,\\n address to,\\n uint256 value,\\n bytes memory data\\n ) internal {\\n if (to.code.length == 0) {\\n safeTransferFrom(token, from, to, value);\\n } else if (!token.transferFromAndCall(from, to, value, data)) {\\n revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no\\n * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when\\n * targeting contracts.\\n *\\n * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.\\n * Oppositely, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}\\n * once without retrying, and relies on the returned value to be true.\\n *\\n * Reverts if the returned value is other than `true`.\\n */\\n function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {\\n if (to.code.length == 0) {\\n forceApprove(token, to, value);\\n } else if (!token.approveAndCall(to, value, data)) {\\n revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Imitates a Solidity `token.transfer(to, value)` call, relaxing the requirement on the return value: the\\n * return value is optional (but if data is returned, it must not be false).\\n *\\n * @param token The token targeted by the call.\\n * @param to The recipient of the tokens\\n * @param value The amount of token to transfer\\n * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.\\n */\\n function _safeTransfer(IERC20 token, address to, uint256 value, bool bubble) private returns (bool success) {\\n bytes4 selector = IERC20.transfer.selector;\\n\\n assembly (\\\"memory-safe\\\") {\\n let fmp := mload(0x40)\\n mstore(0x00, selector)\\n mstore(0x04, and(to, shr(96, not(0))))\\n mstore(0x24, value)\\n success := call(gas(), token, 0, 0x00, 0x44, 0x00, 0x20)\\n // if call success and return is true, all is good.\\n // otherwise (not success or return is not true), we need to perform further checks\\n if iszero(and(success, eq(mload(0x00), 1))) {\\n // if the call was a failure and bubble is enabled, bubble the error\\n if and(iszero(success), bubble) {\\n returndatacopy(fmp, 0x00, returndatasize())\\n revert(fmp, returndatasize())\\n }\\n // if the return value is not true, then the call is only successful if:\\n // - the token address has code\\n // - the returndata is empty\\n success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))\\n }\\n mstore(0x40, fmp)\\n }\\n }\\n\\n /**\\n * @dev Imitates a Solidity `token.transferFrom(from, to, value)` call, relaxing the requirement on the return\\n * value: the return value is optional (but if data is returned, it must not be false).\\n *\\n * @param token The token targeted by the call.\\n * @param from The sender of the tokens\\n * @param to The recipient of the tokens\\n * @param value The amount of token to transfer\\n * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.\\n */\\n function _safeTransferFrom(\\n IERC20 token,\\n address from,\\n address to,\\n uint256 value,\\n bool bubble\\n ) private returns (bool success) {\\n bytes4 selector = IERC20.transferFrom.selector;\\n\\n assembly (\\\"memory-safe\\\") {\\n let fmp := mload(0x40)\\n mstore(0x00, selector)\\n mstore(0x04, and(from, shr(96, not(0))))\\n mstore(0x24, and(to, shr(96, not(0))))\\n mstore(0x44, value)\\n success := call(gas(), token, 0, 0x00, 0x64, 0x00, 0x20)\\n // if call success and return is true, all is good.\\n // otherwise (not success or return is not true), we need to perform further checks\\n if iszero(and(success, eq(mload(0x00), 1))) {\\n // if the call was a failure and bubble is enabled, bubble the error\\n if and(iszero(success), bubble) {\\n returndatacopy(fmp, 0x00, returndatasize())\\n revert(fmp, returndatasize())\\n }\\n // if the return value is not true, then the call is only successful if:\\n // - the token address has code\\n // - the returndata is empty\\n success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))\\n }\\n mstore(0x40, fmp)\\n mstore(0x60, 0)\\n }\\n }\\n\\n /**\\n * @dev Imitates a Solidity `token.approve(spender, value)` call, relaxing the requirement on the return value:\\n * the return value is optional (but if data is returned, it must not be false).\\n *\\n * @param token The token targeted by the call.\\n * @param spender The spender of the tokens\\n * @param value The amount of token to transfer\\n * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.\\n */\\n function _safeApprove(IERC20 token, address spender, uint256 value, bool bubble) private returns (bool success) {\\n bytes4 selector = IERC20.approve.selector;\\n\\n assembly (\\\"memory-safe\\\") {\\n let fmp := mload(0x40)\\n mstore(0x00, selector)\\n mstore(0x04, and(spender, shr(96, not(0))))\\n mstore(0x24, value)\\n success := call(gas(), token, 0, 0x00, 0x44, 0x00, 0x20)\\n // if call success and return is true, all is good.\\n // otherwise (not success or return is not true), we need to perform further checks\\n if iszero(and(success, eq(mload(0x00), 1))) {\\n // if the call was a failure and bubble is enabled, bubble the error\\n if and(iszero(success), bubble) {\\n returndatacopy(fmp, 0x00, returndatasize())\\n revert(fmp, returndatasize())\\n }\\n // if the return value is not true, then the call is only successful if:\\n // - the token address has code\\n // - the returndata is empty\\n success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))\\n }\\n mstore(0x40, fmp)\\n }\\n }\\n}\\n\",\"keccak256\":\"0x304d732678032a9781ae85c8f204c8fba3d3a5e31c02616964e75cfdc5049098\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Context.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\\n\\npragma solidity ^0.8.20;\\n\\n/**\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract Context {\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n\\n function _contextSuffixLength() internal view virtual returns (uint256) {\\n return 0;\\n }\\n}\\n\",\"keccak256\":\"0x493033a8d1b176a037b2cc6a04dad01a5c157722049bbecf632ca876224dd4b2\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/introspection/IERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)\\n\\npragma solidity >=0.4.16;\\n\\n/**\\n * @dev Interface of the ERC-165 standard, as defined in the\\n * https://eips.ethereum.org/EIPS/eip-165[ERC].\\n *\\n * Implementers can declare support of contract interfaces, which can then be\\n * queried by others ({ERC165Checker}).\\n *\\n * For an implementation, see {ERC165}.\\n */\\ninterface IERC165 {\\n /**\\n * @dev Returns true if this contract implements the interface defined by\\n * `interfaceId`. See the corresponding\\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]\\n * to learn more about how these ids are created.\\n *\\n * This function call must use less than 30 000 gas.\\n */\\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x8891738ffe910f0cf2da09566928589bf5d63f4524dd734fd9cedbac3274dd5c\",\"license\":\"MIT\"},\"contracts/MyOFT.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\r\\npragma solidity ^0.8.22;\\r\\n\\r\\nimport { Ownable } from \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\r\\nimport { OFT } from \\\"@layerzerolabs/oft-evm/contracts/OFT.sol\\\";\\r\\n\\r\\ncontract MyOFT is OFT {\\r\\n constructor(\\r\\n string memory _name,\\r\\n string memory _symbol,\\r\\n address _lzEndpoint,\\r\\n address _delegate\\r\\n ) OFT(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) {}\\r\\n\\r\\n /// @notice Open mint, TESTNET ONLY \\u2014 same as ToyOFT, so the demo tasks work against either.\\r\\n /// @dev Deliberately unguarded: the demo mints to itself before each send, and a testnet OFT\\r\\n /// with no way to obtain tokens cannot be used to exercise the DVN at all. Do NOT ship\\r\\n /// this to a network where the token has value.\\r\\n /// `virtual` because MyOFTMock declares the same function for the hardhat tests.\\r\\n function mint(address _to, uint256 _amount) public virtual {\\r\\n _mint(_to, _amount);\\r\\n }\\r\\n}\\r\\n\",\"keccak256\":\"0x742ac02bacb2a1fa397bf560593b446ea96b15f7031123129b0fa6bcbd4c0e80\",\"license\":\"UNLICENSED\"}},\"version\":1}", + "bytecode": "0x60c06040523480156200001157600080fd5b5060405162003790380380620037908339810160408190526200003491620002d2565b83838383838360128484818181818d6001600160a01b0381166200007257604051631e4fbdf760e01b81526000600482015260240160405180910390fd5b6200007d8162000198565b506001600160a01b038083166080528116620000ac57604051632d618d8160e21b815260040160405180910390fd5b60805160405163ca5eb5e160e01b81526001600160a01b0383811660048301529091169063ca5eb5e190602401600060405180830381600087803b158015620000f457600080fd5b505af115801562000109573d6000803e3d6000fd5b505050505050505062000121620001e860201b60201c565b60ff168360ff16101562000148576040516301e9714b60e41b815260040160405180910390fd5b6200015560068462000377565b6200016290600a62000496565b60a052506008915062000178905083826200053f565b5060096200018782826200053f565b50505050505050505050506200060b565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b600690565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126200021557600080fd5b81516001600160401b0380821115620002325762000232620001ed565b604051601f8301601f19908116603f011681019082821181831017156200025d576200025d620001ed565b81604052838152602092508660208588010111156200027b57600080fd5b600091505b838210156200029f578582018301518183018401529082019062000280565b6000602085830101528094505050505092915050565b80516001600160a01b0381168114620002cd57600080fd5b919050565b60008060008060808587031215620002e957600080fd5b84516001600160401b03808211156200030157600080fd5b6200030f8883890162000203565b955060208701519150808211156200032657600080fd5b50620003358782880162000203565b9350506200034660408601620002b5565b91506200035660608601620002b5565b905092959194509250565b634e487b7160e01b600052601160045260246000fd5b60ff828116828216039081111562000393576200039362000361565b92915050565b600181815b80851115620003da578160001904821115620003be57620003be62000361565b80851615620003cc57918102915b93841c93908002906200039e565b509250929050565b600082620003f35750600162000393565b81620004025750600062000393565b81600181146200041b5760028114620004265762000446565b600191505062000393565b60ff8411156200043a576200043a62000361565b50506001821b62000393565b5060208310610133831016604e8410600b84101617156200046b575081810a62000393565b62000477838362000399565b80600019048211156200048e576200048e62000361565b029392505050565b6000620004a760ff841683620003e2565b9392505050565b600181811c90821680620004c357607f821691505b602082108103620004e457634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200053a576000816000526020600020601f850160051c81016020861015620005155750805b601f850160051c820191505b81811015620005365782815560010162000521565b5050505b505050565b81516001600160401b038111156200055b576200055b620001ed565b62000573816200056c8454620004ae565b84620004ea565b602080601f831160018114620005ab5760008415620005925750858301515b600019600386901b1c1916600185901b17855562000536565b600085815260208120601f198616915b82811015620005dc57888601518255948401946001909101908401620005bb565b5085821015620005fb5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60805160a051613119620006776000396000818161064901528181611b0c01528181611b810152611d8601526000818161050801528181610a78015281816110d201528181611349015281816116b401528181611eab01528181611fe5015261209e01526131196000f3fe60806040526004361061025c5760003560e01c8063715018a611610144578063bb0b6a53116100b6578063d045a0dc1161007a578063d045a0dc14610780578063d424388514610793578063dd62ed3e146107b3578063f2fde38b146107f9578063fc0c546a1461048c578063ff7bd03d1461081957600080fd5b8063bb0b6a53146106df578063bc70b3541461070c578063bd815db01461072c578063c7c7f5b31461073f578063ca5eb5e11461076057600080fd5b806395d89b411161010857806395d89b4114610622578063963efcaa146106375780639f68b9641461066b578063a9059cbb1461067f578063b731ea0a1461069f578063b98bd070146106bf57600080fd5b8063715018a6146105805780637d25a05e1461059557806382413eac146105d0578063857749b0146105f05780638da5cb5b1461060457600080fd5b806323b872dd116101dd57806352ae2879116101a157806352ae28791461048c5780635535d4611461049f5780635a0dfe4d146104bf5780635e280f11146104f65780636fc1b31e1461052a57806370a082311461054a57600080fd5b806323b872dd146103dd578063313ce567146103fd5780633400288b1461041f5780633b6f743b1461043f57806340c10f191461046c57600080fd5b8063134d4f2511610224578063134d4f2514610338578063156a0d0f1461036057806317442b701461038757806318160ddd146103a95780631f5e1334146103c857600080fd5b806306fdde0314610261578063095ea7b31461028c5780630d35b415146102bc578063111ecdad146102eb57806313137d6514610323575b600080fd5b34801561026d57600080fd5b50610276610839565b60405161028391906121fd565b60405180910390f35b34801561029857600080fd5b506102ac6102a7366004612225565b6108cb565b6040519015158152602001610283565b3480156102c857600080fd5b506102dc6102d7366004612269565b6108e5565b6040516102839392919061229d565b3480156102f757600080fd5b5060045461030b906001600160a01b031681565b6040516001600160a01b039091168152602001610283565b610336610331366004612390565b610a76565b005b34801561034457600080fd5b5061034d600281565b60405161ffff9091168152602001610283565b34801561036c57600080fd5b506040805162b9270b60e21b81526001602082015201610283565b34801561039357600080fd5b5060408051600181526002602082015201610283565b3480156103b557600080fd5b506007545b604051908152602001610283565b3480156103d457600080fd5b5061034d600181565b3480156103e957600080fd5b506102ac6103f836600461242f565b610b36565b34801561040957600080fd5b5060125b60405160ff9091168152602001610283565b34801561042b57600080fd5b5061033661043a366004612489565b610b5c565b34801561044b57600080fd5b5061045f61045a3660046124b3565b610b72565b6040516102839190612504565b34801561047857600080fd5b50610336610487366004612225565b610bd9565b34801561049857600080fd5b503061030b565b3480156104ab57600080fd5b506102766104ba36600461252d565b610be3565b3480156104cb57600080fd5b506102ac6104da366004612489565b63ffffffff919091166000908152600160205260409020541490565b34801561050257600080fd5b5061030b7f000000000000000000000000000000000000000000000000000000000000000081565b34801561053657600080fd5b50610336610545366004612560565b610c88565b34801561055657600080fd5b506103ba610565366004612560565b6001600160a01b031660009081526005602052604090205490565b34801561058c57600080fd5b50610336610ce5565b3480156105a157600080fd5b506105b86105b0366004612489565b600092915050565b6040516001600160401b039091168152602001610283565b3480156105dc57600080fd5b506102ac6105eb36600461257d565b610cf9565b3480156105fc57600080fd5b50600661040d565b34801561061057600080fd5b506000546001600160a01b031661030b565b34801561062e57600080fd5b50610276610d0e565b34801561064357600080fd5b506103ba7f000000000000000000000000000000000000000000000000000000000000000081565b34801561067757600080fd5b5060006102ac565b34801561068b57600080fd5b506102ac61069a366004612225565b610d1d565b3480156106ab57600080fd5b5060025461030b906001600160a01b031681565b3480156106cb57600080fd5b506103366106da366004612627565b610d2b565b3480156106eb57600080fd5b506103ba6106fa366004612668565b60016020526000908152604090205481565b34801561071857600080fd5b50610276610727366004612683565b610d45565b61033661073a366004612627565b610eed565b61075261074d3660046126e3565b611077565b604051610283929190612750565b34801561076c57600080fd5b5061033661077b366004612560565b6110ab565b61033661078e366004612390565b611131565b34801561079f57600080fd5b506103366107ae366004612560565b611160565b3480156107bf57600080fd5b506103ba6107ce3660046127a2565b6001600160a01b03918216600090815260066020908152604080832093909416825291909152205490565b34801561080557600080fd5b50610336610814366004612560565b6111b6565b34801561082557600080fd5b506102ac6108343660046127d0565b6111f4565b606060088054610848906127ec565b80601f0160208091040260200160405190810160405280929190818152602001828054610874906127ec565b80156108c15780601f10610896576101008083540402835291602001916108c1565b820191906000526020600020905b8154815290600101906020018083116108a457829003601f168201915b5050505050905090565b6000336108d981858561122a565b60019150505b92915050565b60408051808201909152600080825260208201526060610918604051806040016040528060008152602001600081525090565b600080306001600160a01b031663fc0c546a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610959573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061097d9190612820565b6001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109ba573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109de919061283d565b60408051808201825284815260208082018490528251600080825291810190935290975091925090610a33565b604080518082019091526000815260606020820152815260200190600190039081610a0b5790505b509350600080610a58604089013560608a0135610a5360208c018c612668565b61123c565b60408051808201909152918252602082015296989597505050505050565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163314610ac6576040516391ac5e4f60e01b81523360048201526024015b60405180910390fd5b60208701803590610ae090610adb908a612668565b611278565b14610b1e57610af26020880188612668565b60405163309afaf360e21b815263ffffffff909116600482015260208801356024820152604401610abd565b610b2d878787878787876112b4565b50505050505050565b600033610b4485828561141b565b610b4f85858561149a565b60019150505b9392505050565b610b646114f9565b610b6e8282611526565b5050565b60408051808201909152600080825260208201526000610ba260408501356060860135610a536020880188612668565b915050600080610bb2868461157b565b9092509050610bcf610bc76020880188612668565b83838861169e565b9695505050505050565b610b6e828261177f565b600360209081526000928352604080842090915290825290208054610c07906127ec565b80601f0160208091040260200160405190810160405280929190818152602001828054610c33906127ec565b8015610c805780601f10610c5557610100808354040283529160200191610c80565b820191906000526020600020905b815481529060010190602001808311610c6357829003601f168201915b505050505081565b610c906114f9565b600480546001600160a01b0319166001600160a01b0383169081179091556040519081527ff0be4f1e87349231d80c36b33f9e8639658eeaf474014dee15a3e6a4d4414197906020015b60405180910390a150565b610ced6114f9565b610cf760006117b5565b565b6001600160a01b03811630145b949350505050565b606060098054610848906127ec565b6000336108d981858561149a565b610d336114f9565b610b6e610d40828461290d565b611805565b63ffffffff8416600090815260036020908152604080832061ffff87168452909152812080546060929190610d79906127ec565b80601f0160208091040260200160405190810160405280929190818152602001828054610da5906127ec565b8015610df25780601f10610dc757610100808354040283529160200191610df2565b820191906000526020600020905b815481529060010190602001808311610dd557829003601f168201915b505050505090508051600003610e425783838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929450610d069350505050565b6000839003610e52579050610d06565b60028310610ed057610e9984848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061190c92505050565b80610ea78460028188612a22565b604051602001610eb993929190612a4c565b604051602081830303815290604052915050610d06565b8383604051639a6d49cd60e01b8152600401610abd929190612a9d565b60005b81811015610ff65736838383818110610f0b57610f0b612ab1565b9050602002810190610f1d9190612ac7565b9050610f50610f2f6020830183612668565b602083013563ffffffff919091166000908152600160205260409020541490565b610f5a5750610fee565b3063d045a0dc60c08301358360a0810135610f79610100830183612ae8565b610f8a610100890160e08a01612560565b610f986101208a018a612ae8565b6040518963ffffffff1660e01b8152600401610fba9796959493929190612b43565b6000604051808303818588803b158015610fd357600080fd5b505af1158015610fe7573d6000803e3d6000fd5b5050505050505b600101610ef0565b50336001600160a01b0316638e9e70996040518163ffffffff1660e01b8152600401600060405180830381865afa158015611035573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261105d9190810190612bc9565b604051638351eea760e01b8152600401610abd91906121fd565b61107f612166565b604080518082019091526000808252602082015261109e858585611938565b915091505b935093915050565b6110b36114f9565b60405163ca5eb5e160e01b81526001600160a01b0382811660048301527f0000000000000000000000000000000000000000000000000000000000000000169063ca5eb5e190602401600060405180830381600087803b15801561111657600080fd5b505af115801561112a573d6000803e3d6000fd5b5050505050565b3330146111515760405163029a949d60e31b815260040160405180910390fd5b610b2d87878787878787610b1e565b6111686114f9565b600280546001600160a01b0319166001600160a01b0383169081179091556040519081527fd48d879cef83a1c0bdda516f27b13ddb1b3f8bbac1c9e1511bb2a659c242776090602001610cda565b6111be6114f9565b6001600160a01b0381166111e857604051631e4fbdf760e01b815260006004820152602401610abd565b6111f1816117b5565b50565b600060208201803590600190839061120c9086612668565b63ffffffff1681526020810191909152604001600020541492915050565b6112378383836001611a33565b505050565b60008061124885611b08565b9150819050838110156110a3576040516371c4efed60e01b81526004810182905260248101859052604401610abd565b63ffffffff8116600090815260016020526040812054806108df5760405163f6ff4fb760e01b815263ffffffff84166004820152602401610abd565b60006112c66112c38787611b3f565b90565b905060006112f2826112e06112db8a8a611b57565b611b7a565b6112ed60208d018d612668565b611baf565b905060288611156113b957600061132f61131260608c0160408d01612c36565b61131f60208d018d612668565b8461132a8c8c611bd7565b611c22565b604051633e5ac80960e11b81529091506001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690637cb59012906113859086908d906000908790600401612c53565b600060405180830381600087803b15801561139f57600080fd5b505af11580156113b3573d6000803e3d6000fd5b50505050505b6001600160a01b038216887fefed6d3500546b29533b128a29e3a94d70788727f0507505ac12eaf2e578fd9c6113f260208d018d612668565b6040805163ffffffff9092168252602082018690520160405180910390a3505050505050505050565b6001600160a01b03838116600090815260066020908152604080832093861683529290522054600019811015611494578181101561148557604051637dc7a0d960e11b81526001600160a01b03841660048201526024810182905260448101839052606401610abd565b61149484848484036000611a33565b50505050565b6001600160a01b0383166114c457604051634b637e8f60e11b815260006004820152602401610abd565b6001600160a01b0382166114ee5760405163ec442f0560e01b815260006004820152602401610abd565b611237838383611c54565b6000546001600160a01b03163314610cf75760405163118cdaa760e01b8152336004820152602401610abd565b63ffffffff8216600081815260016020908152604091829020849055815192835282018390527f238399d427b947898edb290f5ff0f9109849b1c3ba196a42e35f00c50a54b98b910160405180910390a15050565b60608060006115d8856020013561159186611d7e565b61159e60a0890189612ae8565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611dd892505050565b90935090506000816115eb5760016115ee565b60025b905061160e6116006020880188612668565b8261072760808a018a612ae8565b6004549093506001600160a01b031680156116945760405163043a78eb60e01b81526001600160a01b0382169063043a78eb906116519088908890600401612c84565b602060405180830381865afa15801561166e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116929190612ca9565b505b5050509250929050565b60408051808201909152600080825260208201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663ddc28c586040518060a001604052808863ffffffff16815260200161170189611278565b8152602001878152602001868152602001851515815250306040518363ffffffff1660e01b8152600401611736929190612cc6565b6040805180830381865afa158015611752573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117769190612d6f565b95945050505050565b6001600160a01b0382166117a95760405163ec442f0560e01b815260006004820152602401610abd565b610b6e60008383611c54565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60005b81518110156118dc5761183782828151811061182657611826612ab1565b60200260200101516040015161190c565b81818151811061184957611849612ab1565b6020026020010151604001516003600084848151811061186b5761186b612ab1565b60200260200101516000015163ffffffff1663ffffffff16815260200190815260200160002060008484815181106118a5576118a5612ab1565b60200260200101516020015161ffff1661ffff16815260200190815260200160002090816118d39190612ddb565b50600101611808565b507fbe4864a8e820971c0247f5992e2da559595f7bf076a21cb5928d443d2a13b67481604051610cda9190612e9a565b600281015161ffff8116600314610b6e5781604051639a6d49cd60e01b8152600401610abd91906121fd565b611940612166565b604080518082019091526000808252602082015260008061197733604089013560608a013561197260208c018c612668565b611e52565b91509150600080611988898461157b565b90925090506119b461199d60208b018b612668565b83836119ae368d90038d018d612f25565b8b611e78565b60408051808201909152858152602080820186905282519298509096503391907f85496b760a4b7f8d66384b9df21b381f5d1b1e79f229a47aaf4c232edc2fe59a90611a02908d018d612668565b6040805163ffffffff909216825260208201899052810187905260600160405180910390a350505050935093915050565b6001600160a01b038416611a5d5760405163e602df0560e01b815260006004820152602401610abd565b6001600160a01b038316611a8757604051634a1406b160e11b815260006004820152602401610abd565b6001600160a01b038085166000908152600660209081526040808320938716835292905220829055801561149457826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92584604051611afa91815260200190565b60405180910390a350505050565b60007f0000000000000000000000000000000000000000000000000000000000000000611b358184612f6d565b6108df9190612f8f565b6000611b4e6020828486612a22565b610b5591612fa6565b6000611b67602860208486612a22565b611b7091612fc4565b60c01c9392505050565b60006108df7f00000000000000000000000000000000000000000000000000000000000000006001600160401b038416612f8f565b60006001600160a01b038416611bc55761dead93505b611bcf848461177f565b509092915050565b6060611be68260288186612a22565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929695505050505050565b606084848484604051602001611c3b9493929190612ff4565b6040516020818303038152906040529050949350505050565b6001600160a01b038316611c7f578060076000828254611c749190613043565b90915550611cf19050565b6001600160a01b03831660009081526005602052604090205481811015611cd25760405163391434e360e21b81526001600160a01b03851660048201526024810182905260448101839052606401610abd565b6001600160a01b03841660009081526005602052604090209082900390555b6001600160a01b038216611d0d57600780548290039055611d2c565b6001600160a01b03821660009081526005602052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051611d7191815260200190565b60405180910390a3505050565b600080611dab7f000000000000000000000000000000000000000000000000000000000000000084612f6d565b90506001600160401b038111156108df5760405163e2ce941360e01b815260048101829052602401610abd565b8051606090151580611e21578484604051602001611e0d92919091825260c01b6001600160c01b031916602082015260280190565b604051602081830303815290604052611e48565b84843385604051602001611e389493929190613056565b6040516020818303038152906040525b9150935093915050565b600080611e6085858561123c565b9092509050611e6f8683611f83565b94509492505050565b611e80612166565b6000611e8f8460000151611fb9565b602085015190915015611ea957611ea98460200151611fe1565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316632637a450826040518060a001604052808b63ffffffff168152602001611ef98c611278565b81526020018a815260200189815260200160008960200151111515815250866040518463ffffffff1660e01b8152600401611f35929190612cc6565b60806040518083038185885af1158015611f53573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190611f789190613099565b979650505050505050565b6001600160a01b038216611fad57604051634b637e8f60e11b815260006004820152602401610abd565b610b6e82600083611c54565b6000813414611fdd576040516304fb820960e51b8152346004820152602401610abd565b5090565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663e4fe1d946040518163ffffffff1660e01b8152600401602060405180830381865afa158015612041573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120659190612820565b90506001600160a01b03811661208e576040516329b99a9560e11b815260040160405180910390fd5b610b6e6001600160a01b038216337f0000000000000000000000000000000000000000000000000000000000000000856120cc8484848460016120f4565b61149457604051635274afe760e01b81526001600160a01b0385166004820152602401610abd565b6040516323b872dd60e01b60008181526001600160a01b038781166004528616602452604485905291602083606481808c5af192506001600051148316612154578383151615612147573d6000823e3d81fd5b6000883b113d1516831692505b60405250600060605295945050505050565b60405180606001604052806000801916815260200160006001600160401b031681526020016121a8604051806040016040528060008152602001600081525090565b905290565b60005b838110156121c85781810151838201526020016121b0565b50506000910152565b600081518084526121e98160208601602086016121ad565b601f01601f19169290920160200192915050565b602081526000610b5560208301846121d1565b6001600160a01b03811681146111f157600080fd5b6000806040838503121561223857600080fd5b823561224381612210565b946020939093013593505050565b600060e0828403121561226357600080fd5b50919050565b60006020828403121561227b57600080fd5b81356001600160401b0381111561229157600080fd5b610d0684828501612251565b8351815260208085015190820152600060a08201604060a0604085015281865180845260c08601915060c08160051b8701019350602080890160005b838110156123185788870360bf19018552815180518852830151838801879052612305878901826121d1565b97505093820193908201906001016122d9565b50508751606088015250505060208501516080850152509050610d06565b60006060828403121561226357600080fd5b60008083601f84011261235a57600080fd5b5081356001600160401b0381111561237157600080fd5b60208301915083602082850101111561238957600080fd5b9250929050565b600080600080600080600060e0888a0312156123ab57600080fd5b6123b58989612336565b96506060880135955060808801356001600160401b03808211156123d857600080fd5b6123e48b838c01612348565b909750955060a08a013591506123f982612210565b90935060c0890135908082111561240f57600080fd5b5061241c8a828b01612348565b989b979a50959850939692959293505050565b60008060006060848603121561244457600080fd5b833561244f81612210565b9250602084013561245f81612210565b929592945050506040919091013590565b803563ffffffff8116811461248457600080fd5b919050565b6000806040838503121561249c57600080fd5b61224383612470565b80151581146111f157600080fd5b600080604083850312156124c657600080fd5b82356001600160401b038111156124dc57600080fd5b6124e885828601612251565b92505060208301356124f9816124a5565b809150509250929050565b8151815260208083015190820152604081016108df565b803561ffff8116811461248457600080fd5b6000806040838503121561254057600080fd5b61254983612470565b91506125576020840161251b565b90509250929050565b60006020828403121561257257600080fd5b8135610b5581612210565b60008060008060a0858703121561259357600080fd5b61259d8686612336565b935060608501356001600160401b038111156125b857600080fd5b6125c487828801612348565b90945092505060808501356125d881612210565b939692955090935050565b60008083601f8401126125f557600080fd5b5081356001600160401b0381111561260c57600080fd5b6020830191508360208260051b850101111561238957600080fd5b6000806020838503121561263a57600080fd5b82356001600160401b0381111561265057600080fd5b61265c858286016125e3565b90969095509350505050565b60006020828403121561267a57600080fd5b610b5582612470565b6000806000806060858703121561269957600080fd5b6126a285612470565b93506126b06020860161251b565b925060408501356001600160401b038111156126cb57600080fd5b6126d787828801612348565b95989497509550505050565b600080600083850360808112156126f957600080fd5b84356001600160401b0381111561270f57600080fd5b61271b87828801612251565b9450506040601f198201121561273057600080fd5b50602084019150606084013561274581612210565b809150509250925092565b600060c082019050835182526001600160401b036020850151166020830152604084015161278b604084018280518252602090810151910152565b5082516080830152602083015160a0830152610b55565b600080604083850312156127b557600080fd5b82356127c081612210565b915060208301356124f981612210565b6000606082840312156127e257600080fd5b610b558383612336565b600181811c9082168061280057607f821691505b60208210810361226357634e487b7160e01b600052602260045260246000fd5b60006020828403121561283257600080fd5b8151610b5581612210565b60006020828403121561284f57600080fd5b5051919050565b634e487b7160e01b600052604160045260246000fd5b604051606081016001600160401b038111828210171561288e5761288e612856565b60405290565b604080519081016001600160401b038111828210171561288e5761288e612856565b604051601f8201601f191681016001600160401b03811182821017156128de576128de612856565b604052919050565b60006001600160401b038211156128ff576128ff612856565b50601f01601f191660200190565b60006001600160401b038084111561292757612927612856565b8360051b60206129388183016128b6565b86815291850191818101903684111561295057600080fd5b865b84811015612a165780358681111561296a5760008081fd5b8801606036829003121561297e5760008081fd5b61298661286c565b61298f82612470565b815261299c86830161251b565b86820152604080830135898111156129b45760008081fd5b929092019136601f8401126129c95760008081fd5b82356129dc6129d7826128e6565b6128b6565b81815236898387010111156129f15760008081fd5b818986018a830137600091810189019190915290820152845250918301918301612952565b50979650505050505050565b60008085851115612a3257600080fd5b83861115612a3f57600080fd5b5050820193919092039150565b60008451612a5e8184602089016121ad565b8201838582376000930192835250909392505050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b602081526000610d06602083018486612a74565b634e487b7160e01b600052603260045260246000fd5b6000823561013e19833603018112612ade57600080fd5b9190910192915050565b6000808335601e19843603018112612aff57600080fd5b8301803591506001600160401b03821115612b1957600080fd5b60200191503681900382131561238957600080fd5b6001600160401b03811681146111f157600080fd5b63ffffffff612b5189612470565b1681526020880135602082015260006040890135612b6e81612b2e565b6001600160401b03811660408401525087606083015260e06080830152612b9960e083018789612a74565b6001600160a01b03861660a084015282810360c0840152612bbb818587612a74565b9a9950505050505050505050565b600060208284031215612bdb57600080fd5b81516001600160401b03811115612bf157600080fd5b8201601f81018413612c0257600080fd5b8051612c106129d7826128e6565b818152856020838501011115612c2557600080fd5b6117768260208301602086016121ad565b600060208284031215612c4857600080fd5b8135610b5581612b2e565b60018060a01b038516815283602082015261ffff83166040820152608060608201526000610bcf60808301846121d1565b604081526000612c9760408301856121d1565b828103602084015261177681856121d1565b600060208284031215612cbb57600080fd5b8151610b55816124a5565b6040815263ffffffff8351166040820152602083015160608201526000604084015160a06080840152612cfc60e08401826121d1565b90506060850151603f198483030160a0850152612d1982826121d1565b60809690960151151560c08501525050506001600160a01b039190911660209091015290565b600060408284031215612d5157600080fd5b612d59612894565b9050815181526020820151602082015292915050565b600060408284031215612d8157600080fd5b610b558383612d3f565b601f821115611237576000816000526020600020601f850160051c81016020861015612db45750805b601f850160051c820191505b81811015612dd357828155600101612dc0565b505050505050565b81516001600160401b03811115612df457612df4612856565b612e0881612e0284546127ec565b84612d8b565b602080601f831160018114612e3d5760008415612e255750858301515b600019600386901b1c1916600185901b178555612dd3565b600085815260208120601f198616915b82811015612e6c57888601518255948401946001909101908401612e4d565b5085821015612e8a5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b600060208083018184528085518083526040925060408601915060408160051b87010184880160005b83811015612f1757888303603f190185528151805163ffffffff1684528781015161ffff16888501528601516060878501819052612f03818601836121d1565b968901969450505090860190600101612ec3565b509098975050505050505050565b600060408284031215612f3757600080fd5b612f3f612894565b82358152602083013560208201528091505092915050565b634e487b7160e01b600052601160045260246000fd5b600082612f8a57634e487b7160e01b600052601260045260246000fd5b500490565b80820281158282048414176108df576108df612f57565b803560208310156108df57600019602084900360031b1b1692915050565b6001600160c01b03198135818116916008851015612fec5780818660080360031b1b83161692505b505092915050565b6001600160401b0360c01b8560c01b16815263ffffffff60e01b8460e01b16600882015282600c8201526000825161303381602c8501602087016121ad565b91909101602c0195945050505050565b808201808211156108df576108df612f57565b8481526001600160401b0360c01b8460c01b166020820152826028820152600082516130898160488501602087016121ad565b9190910160480195945050505050565b6000608082840312156130ab57600080fd5b6130b361286c565b8251815260208301516130c581612b2e565b60208201526130d78460408501612d3f565b6040820152939250505056fea2646970667358221220aa404cddf55107bb4ef2c6beb0224c8f9f889ea8535030db0d36c362777d7cae64736f6c63430008160033", + "deployedBytecode": "0x60806040526004361061025c5760003560e01c8063715018a611610144578063bb0b6a53116100b6578063d045a0dc1161007a578063d045a0dc14610780578063d424388514610793578063dd62ed3e146107b3578063f2fde38b146107f9578063fc0c546a1461048c578063ff7bd03d1461081957600080fd5b8063bb0b6a53146106df578063bc70b3541461070c578063bd815db01461072c578063c7c7f5b31461073f578063ca5eb5e11461076057600080fd5b806395d89b411161010857806395d89b4114610622578063963efcaa146106375780639f68b9641461066b578063a9059cbb1461067f578063b731ea0a1461069f578063b98bd070146106bf57600080fd5b8063715018a6146105805780637d25a05e1461059557806382413eac146105d0578063857749b0146105f05780638da5cb5b1461060457600080fd5b806323b872dd116101dd57806352ae2879116101a157806352ae28791461048c5780635535d4611461049f5780635a0dfe4d146104bf5780635e280f11146104f65780636fc1b31e1461052a57806370a082311461054a57600080fd5b806323b872dd146103dd578063313ce567146103fd5780633400288b1461041f5780633b6f743b1461043f57806340c10f191461046c57600080fd5b8063134d4f2511610224578063134d4f2514610338578063156a0d0f1461036057806317442b701461038757806318160ddd146103a95780631f5e1334146103c857600080fd5b806306fdde0314610261578063095ea7b31461028c5780630d35b415146102bc578063111ecdad146102eb57806313137d6514610323575b600080fd5b34801561026d57600080fd5b50610276610839565b60405161028391906121fd565b60405180910390f35b34801561029857600080fd5b506102ac6102a7366004612225565b6108cb565b6040519015158152602001610283565b3480156102c857600080fd5b506102dc6102d7366004612269565b6108e5565b6040516102839392919061229d565b3480156102f757600080fd5b5060045461030b906001600160a01b031681565b6040516001600160a01b039091168152602001610283565b610336610331366004612390565b610a76565b005b34801561034457600080fd5b5061034d600281565b60405161ffff9091168152602001610283565b34801561036c57600080fd5b506040805162b9270b60e21b81526001602082015201610283565b34801561039357600080fd5b5060408051600181526002602082015201610283565b3480156103b557600080fd5b506007545b604051908152602001610283565b3480156103d457600080fd5b5061034d600181565b3480156103e957600080fd5b506102ac6103f836600461242f565b610b36565b34801561040957600080fd5b5060125b60405160ff9091168152602001610283565b34801561042b57600080fd5b5061033661043a366004612489565b610b5c565b34801561044b57600080fd5b5061045f61045a3660046124b3565b610b72565b6040516102839190612504565b34801561047857600080fd5b50610336610487366004612225565b610bd9565b34801561049857600080fd5b503061030b565b3480156104ab57600080fd5b506102766104ba36600461252d565b610be3565b3480156104cb57600080fd5b506102ac6104da366004612489565b63ffffffff919091166000908152600160205260409020541490565b34801561050257600080fd5b5061030b7f000000000000000000000000000000000000000000000000000000000000000081565b34801561053657600080fd5b50610336610545366004612560565b610c88565b34801561055657600080fd5b506103ba610565366004612560565b6001600160a01b031660009081526005602052604090205490565b34801561058c57600080fd5b50610336610ce5565b3480156105a157600080fd5b506105b86105b0366004612489565b600092915050565b6040516001600160401b039091168152602001610283565b3480156105dc57600080fd5b506102ac6105eb36600461257d565b610cf9565b3480156105fc57600080fd5b50600661040d565b34801561061057600080fd5b506000546001600160a01b031661030b565b34801561062e57600080fd5b50610276610d0e565b34801561064357600080fd5b506103ba7f000000000000000000000000000000000000000000000000000000000000000081565b34801561067757600080fd5b5060006102ac565b34801561068b57600080fd5b506102ac61069a366004612225565b610d1d565b3480156106ab57600080fd5b5060025461030b906001600160a01b031681565b3480156106cb57600080fd5b506103366106da366004612627565b610d2b565b3480156106eb57600080fd5b506103ba6106fa366004612668565b60016020526000908152604090205481565b34801561071857600080fd5b50610276610727366004612683565b610d45565b61033661073a366004612627565b610eed565b61075261074d3660046126e3565b611077565b604051610283929190612750565b34801561076c57600080fd5b5061033661077b366004612560565b6110ab565b61033661078e366004612390565b611131565b34801561079f57600080fd5b506103366107ae366004612560565b611160565b3480156107bf57600080fd5b506103ba6107ce3660046127a2565b6001600160a01b03918216600090815260066020908152604080832093909416825291909152205490565b34801561080557600080fd5b50610336610814366004612560565b6111b6565b34801561082557600080fd5b506102ac6108343660046127d0565b6111f4565b606060088054610848906127ec565b80601f0160208091040260200160405190810160405280929190818152602001828054610874906127ec565b80156108c15780601f10610896576101008083540402835291602001916108c1565b820191906000526020600020905b8154815290600101906020018083116108a457829003601f168201915b5050505050905090565b6000336108d981858561122a565b60019150505b92915050565b60408051808201909152600080825260208201526060610918604051806040016040528060008152602001600081525090565b600080306001600160a01b031663fc0c546a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610959573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061097d9190612820565b6001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109ba573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109de919061283d565b60408051808201825284815260208082018490528251600080825291810190935290975091925090610a33565b604080518082019091526000815260606020820152815260200190600190039081610a0b5790505b509350600080610a58604089013560608a0135610a5360208c018c612668565b61123c565b60408051808201909152918252602082015296989597505050505050565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163314610ac6576040516391ac5e4f60e01b81523360048201526024015b60405180910390fd5b60208701803590610ae090610adb908a612668565b611278565b14610b1e57610af26020880188612668565b60405163309afaf360e21b815263ffffffff909116600482015260208801356024820152604401610abd565b610b2d878787878787876112b4565b50505050505050565b600033610b4485828561141b565b610b4f85858561149a565b60019150505b9392505050565b610b646114f9565b610b6e8282611526565b5050565b60408051808201909152600080825260208201526000610ba260408501356060860135610a536020880188612668565b915050600080610bb2868461157b565b9092509050610bcf610bc76020880188612668565b83838861169e565b9695505050505050565b610b6e828261177f565b600360209081526000928352604080842090915290825290208054610c07906127ec565b80601f0160208091040260200160405190810160405280929190818152602001828054610c33906127ec565b8015610c805780601f10610c5557610100808354040283529160200191610c80565b820191906000526020600020905b815481529060010190602001808311610c6357829003601f168201915b505050505081565b610c906114f9565b600480546001600160a01b0319166001600160a01b0383169081179091556040519081527ff0be4f1e87349231d80c36b33f9e8639658eeaf474014dee15a3e6a4d4414197906020015b60405180910390a150565b610ced6114f9565b610cf760006117b5565b565b6001600160a01b03811630145b949350505050565b606060098054610848906127ec565b6000336108d981858561149a565b610d336114f9565b610b6e610d40828461290d565b611805565b63ffffffff8416600090815260036020908152604080832061ffff87168452909152812080546060929190610d79906127ec565b80601f0160208091040260200160405190810160405280929190818152602001828054610da5906127ec565b8015610df25780601f10610dc757610100808354040283529160200191610df2565b820191906000526020600020905b815481529060010190602001808311610dd557829003601f168201915b505050505090508051600003610e425783838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929450610d069350505050565b6000839003610e52579050610d06565b60028310610ed057610e9984848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061190c92505050565b80610ea78460028188612a22565b604051602001610eb993929190612a4c565b604051602081830303815290604052915050610d06565b8383604051639a6d49cd60e01b8152600401610abd929190612a9d565b60005b81811015610ff65736838383818110610f0b57610f0b612ab1565b9050602002810190610f1d9190612ac7565b9050610f50610f2f6020830183612668565b602083013563ffffffff919091166000908152600160205260409020541490565b610f5a5750610fee565b3063d045a0dc60c08301358360a0810135610f79610100830183612ae8565b610f8a610100890160e08a01612560565b610f986101208a018a612ae8565b6040518963ffffffff1660e01b8152600401610fba9796959493929190612b43565b6000604051808303818588803b158015610fd357600080fd5b505af1158015610fe7573d6000803e3d6000fd5b5050505050505b600101610ef0565b50336001600160a01b0316638e9e70996040518163ffffffff1660e01b8152600401600060405180830381865afa158015611035573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261105d9190810190612bc9565b604051638351eea760e01b8152600401610abd91906121fd565b61107f612166565b604080518082019091526000808252602082015261109e858585611938565b915091505b935093915050565b6110b36114f9565b60405163ca5eb5e160e01b81526001600160a01b0382811660048301527f0000000000000000000000000000000000000000000000000000000000000000169063ca5eb5e190602401600060405180830381600087803b15801561111657600080fd5b505af115801561112a573d6000803e3d6000fd5b5050505050565b3330146111515760405163029a949d60e31b815260040160405180910390fd5b610b2d87878787878787610b1e565b6111686114f9565b600280546001600160a01b0319166001600160a01b0383169081179091556040519081527fd48d879cef83a1c0bdda516f27b13ddb1b3f8bbac1c9e1511bb2a659c242776090602001610cda565b6111be6114f9565b6001600160a01b0381166111e857604051631e4fbdf760e01b815260006004820152602401610abd565b6111f1816117b5565b50565b600060208201803590600190839061120c9086612668565b63ffffffff1681526020810191909152604001600020541492915050565b6112378383836001611a33565b505050565b60008061124885611b08565b9150819050838110156110a3576040516371c4efed60e01b81526004810182905260248101859052604401610abd565b63ffffffff8116600090815260016020526040812054806108df5760405163f6ff4fb760e01b815263ffffffff84166004820152602401610abd565b60006112c66112c38787611b3f565b90565b905060006112f2826112e06112db8a8a611b57565b611b7a565b6112ed60208d018d612668565b611baf565b905060288611156113b957600061132f61131260608c0160408d01612c36565b61131f60208d018d612668565b8461132a8c8c611bd7565b611c22565b604051633e5ac80960e11b81529091506001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690637cb59012906113859086908d906000908790600401612c53565b600060405180830381600087803b15801561139f57600080fd5b505af11580156113b3573d6000803e3d6000fd5b50505050505b6001600160a01b038216887fefed6d3500546b29533b128a29e3a94d70788727f0507505ac12eaf2e578fd9c6113f260208d018d612668565b6040805163ffffffff9092168252602082018690520160405180910390a3505050505050505050565b6001600160a01b03838116600090815260066020908152604080832093861683529290522054600019811015611494578181101561148557604051637dc7a0d960e11b81526001600160a01b03841660048201526024810182905260448101839052606401610abd565b61149484848484036000611a33565b50505050565b6001600160a01b0383166114c457604051634b637e8f60e11b815260006004820152602401610abd565b6001600160a01b0382166114ee5760405163ec442f0560e01b815260006004820152602401610abd565b611237838383611c54565b6000546001600160a01b03163314610cf75760405163118cdaa760e01b8152336004820152602401610abd565b63ffffffff8216600081815260016020908152604091829020849055815192835282018390527f238399d427b947898edb290f5ff0f9109849b1c3ba196a42e35f00c50a54b98b910160405180910390a15050565b60608060006115d8856020013561159186611d7e565b61159e60a0890189612ae8565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611dd892505050565b90935090506000816115eb5760016115ee565b60025b905061160e6116006020880188612668565b8261072760808a018a612ae8565b6004549093506001600160a01b031680156116945760405163043a78eb60e01b81526001600160a01b0382169063043a78eb906116519088908890600401612c84565b602060405180830381865afa15801561166e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116929190612ca9565b505b5050509250929050565b60408051808201909152600080825260208201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663ddc28c586040518060a001604052808863ffffffff16815260200161170189611278565b8152602001878152602001868152602001851515815250306040518363ffffffff1660e01b8152600401611736929190612cc6565b6040805180830381865afa158015611752573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117769190612d6f565b95945050505050565b6001600160a01b0382166117a95760405163ec442f0560e01b815260006004820152602401610abd565b610b6e60008383611c54565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60005b81518110156118dc5761183782828151811061182657611826612ab1565b60200260200101516040015161190c565b81818151811061184957611849612ab1565b6020026020010151604001516003600084848151811061186b5761186b612ab1565b60200260200101516000015163ffffffff1663ffffffff16815260200190815260200160002060008484815181106118a5576118a5612ab1565b60200260200101516020015161ffff1661ffff16815260200190815260200160002090816118d39190612ddb565b50600101611808565b507fbe4864a8e820971c0247f5992e2da559595f7bf076a21cb5928d443d2a13b67481604051610cda9190612e9a565b600281015161ffff8116600314610b6e5781604051639a6d49cd60e01b8152600401610abd91906121fd565b611940612166565b604080518082019091526000808252602082015260008061197733604089013560608a013561197260208c018c612668565b611e52565b91509150600080611988898461157b565b90925090506119b461199d60208b018b612668565b83836119ae368d90038d018d612f25565b8b611e78565b60408051808201909152858152602080820186905282519298509096503391907f85496b760a4b7f8d66384b9df21b381f5d1b1e79f229a47aaf4c232edc2fe59a90611a02908d018d612668565b6040805163ffffffff909216825260208201899052810187905260600160405180910390a350505050935093915050565b6001600160a01b038416611a5d5760405163e602df0560e01b815260006004820152602401610abd565b6001600160a01b038316611a8757604051634a1406b160e11b815260006004820152602401610abd565b6001600160a01b038085166000908152600660209081526040808320938716835292905220829055801561149457826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92584604051611afa91815260200190565b60405180910390a350505050565b60007f0000000000000000000000000000000000000000000000000000000000000000611b358184612f6d565b6108df9190612f8f565b6000611b4e6020828486612a22565b610b5591612fa6565b6000611b67602860208486612a22565b611b7091612fc4565b60c01c9392505050565b60006108df7f00000000000000000000000000000000000000000000000000000000000000006001600160401b038416612f8f565b60006001600160a01b038416611bc55761dead93505b611bcf848461177f565b509092915050565b6060611be68260288186612a22565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929695505050505050565b606084848484604051602001611c3b9493929190612ff4565b6040516020818303038152906040529050949350505050565b6001600160a01b038316611c7f578060076000828254611c749190613043565b90915550611cf19050565b6001600160a01b03831660009081526005602052604090205481811015611cd25760405163391434e360e21b81526001600160a01b03851660048201526024810182905260448101839052606401610abd565b6001600160a01b03841660009081526005602052604090209082900390555b6001600160a01b038216611d0d57600780548290039055611d2c565b6001600160a01b03821660009081526005602052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051611d7191815260200190565b60405180910390a3505050565b600080611dab7f000000000000000000000000000000000000000000000000000000000000000084612f6d565b90506001600160401b038111156108df5760405163e2ce941360e01b815260048101829052602401610abd565b8051606090151580611e21578484604051602001611e0d92919091825260c01b6001600160c01b031916602082015260280190565b604051602081830303815290604052611e48565b84843385604051602001611e389493929190613056565b6040516020818303038152906040525b9150935093915050565b600080611e6085858561123c565b9092509050611e6f8683611f83565b94509492505050565b611e80612166565b6000611e8f8460000151611fb9565b602085015190915015611ea957611ea98460200151611fe1565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316632637a450826040518060a001604052808b63ffffffff168152602001611ef98c611278565b81526020018a815260200189815260200160008960200151111515815250866040518463ffffffff1660e01b8152600401611f35929190612cc6565b60806040518083038185885af1158015611f53573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190611f789190613099565b979650505050505050565b6001600160a01b038216611fad57604051634b637e8f60e11b815260006004820152602401610abd565b610b6e82600083611c54565b6000813414611fdd576040516304fb820960e51b8152346004820152602401610abd565b5090565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663e4fe1d946040518163ffffffff1660e01b8152600401602060405180830381865afa158015612041573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120659190612820565b90506001600160a01b03811661208e576040516329b99a9560e11b815260040160405180910390fd5b610b6e6001600160a01b038216337f0000000000000000000000000000000000000000000000000000000000000000856120cc8484848460016120f4565b61149457604051635274afe760e01b81526001600160a01b0385166004820152602401610abd565b6040516323b872dd60e01b60008181526001600160a01b038781166004528616602452604485905291602083606481808c5af192506001600051148316612154578383151615612147573d6000823e3d81fd5b6000883b113d1516831692505b60405250600060605295945050505050565b60405180606001604052806000801916815260200160006001600160401b031681526020016121a8604051806040016040528060008152602001600081525090565b905290565b60005b838110156121c85781810151838201526020016121b0565b50506000910152565b600081518084526121e98160208601602086016121ad565b601f01601f19169290920160200192915050565b602081526000610b5560208301846121d1565b6001600160a01b03811681146111f157600080fd5b6000806040838503121561223857600080fd5b823561224381612210565b946020939093013593505050565b600060e0828403121561226357600080fd5b50919050565b60006020828403121561227b57600080fd5b81356001600160401b0381111561229157600080fd5b610d0684828501612251565b8351815260208085015190820152600060a08201604060a0604085015281865180845260c08601915060c08160051b8701019350602080890160005b838110156123185788870360bf19018552815180518852830151838801879052612305878901826121d1565b97505093820193908201906001016122d9565b50508751606088015250505060208501516080850152509050610d06565b60006060828403121561226357600080fd5b60008083601f84011261235a57600080fd5b5081356001600160401b0381111561237157600080fd5b60208301915083602082850101111561238957600080fd5b9250929050565b600080600080600080600060e0888a0312156123ab57600080fd5b6123b58989612336565b96506060880135955060808801356001600160401b03808211156123d857600080fd5b6123e48b838c01612348565b909750955060a08a013591506123f982612210565b90935060c0890135908082111561240f57600080fd5b5061241c8a828b01612348565b989b979a50959850939692959293505050565b60008060006060848603121561244457600080fd5b833561244f81612210565b9250602084013561245f81612210565b929592945050506040919091013590565b803563ffffffff8116811461248457600080fd5b919050565b6000806040838503121561249c57600080fd5b61224383612470565b80151581146111f157600080fd5b600080604083850312156124c657600080fd5b82356001600160401b038111156124dc57600080fd5b6124e885828601612251565b92505060208301356124f9816124a5565b809150509250929050565b8151815260208083015190820152604081016108df565b803561ffff8116811461248457600080fd5b6000806040838503121561254057600080fd5b61254983612470565b91506125576020840161251b565b90509250929050565b60006020828403121561257257600080fd5b8135610b5581612210565b60008060008060a0858703121561259357600080fd5b61259d8686612336565b935060608501356001600160401b038111156125b857600080fd5b6125c487828801612348565b90945092505060808501356125d881612210565b939692955090935050565b60008083601f8401126125f557600080fd5b5081356001600160401b0381111561260c57600080fd5b6020830191508360208260051b850101111561238957600080fd5b6000806020838503121561263a57600080fd5b82356001600160401b0381111561265057600080fd5b61265c858286016125e3565b90969095509350505050565b60006020828403121561267a57600080fd5b610b5582612470565b6000806000806060858703121561269957600080fd5b6126a285612470565b93506126b06020860161251b565b925060408501356001600160401b038111156126cb57600080fd5b6126d787828801612348565b95989497509550505050565b600080600083850360808112156126f957600080fd5b84356001600160401b0381111561270f57600080fd5b61271b87828801612251565b9450506040601f198201121561273057600080fd5b50602084019150606084013561274581612210565b809150509250925092565b600060c082019050835182526001600160401b036020850151166020830152604084015161278b604084018280518252602090810151910152565b5082516080830152602083015160a0830152610b55565b600080604083850312156127b557600080fd5b82356127c081612210565b915060208301356124f981612210565b6000606082840312156127e257600080fd5b610b558383612336565b600181811c9082168061280057607f821691505b60208210810361226357634e487b7160e01b600052602260045260246000fd5b60006020828403121561283257600080fd5b8151610b5581612210565b60006020828403121561284f57600080fd5b5051919050565b634e487b7160e01b600052604160045260246000fd5b604051606081016001600160401b038111828210171561288e5761288e612856565b60405290565b604080519081016001600160401b038111828210171561288e5761288e612856565b604051601f8201601f191681016001600160401b03811182821017156128de576128de612856565b604052919050565b60006001600160401b038211156128ff576128ff612856565b50601f01601f191660200190565b60006001600160401b038084111561292757612927612856565b8360051b60206129388183016128b6565b86815291850191818101903684111561295057600080fd5b865b84811015612a165780358681111561296a5760008081fd5b8801606036829003121561297e5760008081fd5b61298661286c565b61298f82612470565b815261299c86830161251b565b86820152604080830135898111156129b45760008081fd5b929092019136601f8401126129c95760008081fd5b82356129dc6129d7826128e6565b6128b6565b81815236898387010111156129f15760008081fd5b818986018a830137600091810189019190915290820152845250918301918301612952565b50979650505050505050565b60008085851115612a3257600080fd5b83861115612a3f57600080fd5b5050820193919092039150565b60008451612a5e8184602089016121ad565b8201838582376000930192835250909392505050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b602081526000610d06602083018486612a74565b634e487b7160e01b600052603260045260246000fd5b6000823561013e19833603018112612ade57600080fd5b9190910192915050565b6000808335601e19843603018112612aff57600080fd5b8301803591506001600160401b03821115612b1957600080fd5b60200191503681900382131561238957600080fd5b6001600160401b03811681146111f157600080fd5b63ffffffff612b5189612470565b1681526020880135602082015260006040890135612b6e81612b2e565b6001600160401b03811660408401525087606083015260e06080830152612b9960e083018789612a74565b6001600160a01b03861660a084015282810360c0840152612bbb818587612a74565b9a9950505050505050505050565b600060208284031215612bdb57600080fd5b81516001600160401b03811115612bf157600080fd5b8201601f81018413612c0257600080fd5b8051612c106129d7826128e6565b818152856020838501011115612c2557600080fd5b6117768260208301602086016121ad565b600060208284031215612c4857600080fd5b8135610b5581612b2e565b60018060a01b038516815283602082015261ffff83166040820152608060608201526000610bcf60808301846121d1565b604081526000612c9760408301856121d1565b828103602084015261177681856121d1565b600060208284031215612cbb57600080fd5b8151610b55816124a5565b6040815263ffffffff8351166040820152602083015160608201526000604084015160a06080840152612cfc60e08401826121d1565b90506060850151603f198483030160a0850152612d1982826121d1565b60809690960151151560c08501525050506001600160a01b039190911660209091015290565b600060408284031215612d5157600080fd5b612d59612894565b9050815181526020820151602082015292915050565b600060408284031215612d8157600080fd5b610b558383612d3f565b601f821115611237576000816000526020600020601f850160051c81016020861015612db45750805b601f850160051c820191505b81811015612dd357828155600101612dc0565b505050505050565b81516001600160401b03811115612df457612df4612856565b612e0881612e0284546127ec565b84612d8b565b602080601f831160018114612e3d5760008415612e255750858301515b600019600386901b1c1916600185901b178555612dd3565b600085815260208120601f198616915b82811015612e6c57888601518255948401946001909101908401612e4d565b5085821015612e8a5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b600060208083018184528085518083526040925060408601915060408160051b87010184880160005b83811015612f1757888303603f190185528151805163ffffffff1684528781015161ffff16888501528601516060878501819052612f03818601836121d1565b968901969450505090860190600101612ec3565b509098975050505050505050565b600060408284031215612f3757600080fd5b612f3f612894565b82358152602083013560208201528091505092915050565b634e487b7160e01b600052601160045260246000fd5b600082612f8a57634e487b7160e01b600052601260045260246000fd5b500490565b80820281158282048414176108df576108df612f57565b803560208310156108df57600019602084900360031b1b1692915050565b6001600160c01b03198135818116916008851015612fec5780818660080360031b1b83161692505b505092915050565b6001600160401b0360c01b8560c01b16815263ffffffff60e01b8460e01b16600882015282600c8201526000825161303381602c8501602087016121ad565b91909101602c0195945050505050565b808201808211156108df576108df612f57565b8481526001600160401b0360c01b8460c01b166020820152826028820152600082516130898160488501602087016121ad565b9190910160480195945050505050565b6000608082840312156130ab57600080fd5b6130b361286c565b8251815260208301516130c581612b2e565b60208201526130d78460408501612d3f565b6040820152939250505056fea2646970667358221220aa404cddf55107bb4ef2c6beb0224c8f9f889ea8535030db0d36c362777d7cae64736f6c63430008160033", + "devdoc": { + "errors": { + "ERC20InsufficientAllowance(address,uint256,uint256)": [ + { + "details": "Indicates a failure with the `spender`’s `allowance`. Used in transfers.", + "params": { + "allowance": "Amount of tokens a `spender` is allowed to operate with.", + "needed": "Minimum amount required to perform a transfer.", + "spender": "Address that may be allowed to operate on tokens without being their owner." + } + } + ], + "ERC20InsufficientBalance(address,uint256,uint256)": [ + { + "details": "Indicates an error related to the current `balance` of a `sender`. Used in transfers.", + "params": { + "balance": "Current balance for the interacting account.", + "needed": "Minimum amount required to perform a transfer.", + "sender": "Address whose tokens are being transferred." + } + } + ], + "ERC20InvalidApprover(address)": [ + { + "details": "Indicates a failure with the `approver` of a token to be approved. Used in approvals.", + "params": { + "approver": "Address initiating an approval operation." + } + } + ], + "ERC20InvalidReceiver(address)": [ + { + "details": "Indicates a failure with the token `receiver`. Used in transfers.", + "params": { + "receiver": "Address to which tokens are being transferred." + } + } + ], + "ERC20InvalidSender(address)": [ + { + "details": "Indicates a failure with the token `sender`. Used in transfers.", + "params": { + "sender": "Address whose tokens are being transferred." + } + } + ], + "ERC20InvalidSpender(address)": [ + { + "details": "Indicates a failure with the `spender` to be approved. Used in approvals.", + "params": { + "spender": "Address that may be allowed to operate on tokens without being their owner." + } + } + ], + "OwnableInvalidOwner(address)": [ + { + "details": "The owner is not a valid owner account. (eg. `address(0)`)" + } + ], + "OwnableUnauthorizedAccount(address)": [ + { + "details": "The caller account is not authorized to perform an operation." + } + ], + "SafeERC20FailedOperation(address)": [ + { + "details": "An operation with an ERC-20 token failed." + } + ] + }, + "events": { + "Approval(address,address,uint256)": { + "details": "Emitted when the allowance of a `spender` for an `owner` is set by a call to {approve}. `value` is the new allowance." + }, + "PreCrimeSet(address)": { + "details": "Emitted when the preCrime contract address is set.", + "params": { + "preCrimeAddress": "The address of the preCrime contract." + } + }, + "Transfer(address,address,uint256)": { + "details": "Emitted when `value` tokens are moved from one account (`from`) to another (`to`). Note that `value` may be zero." + } + }, + "kind": "dev", + "methods": { + "allowInitializePath((uint32,bytes32,uint64))": { + "details": "This indicates to the endpoint that the OApp has enabled msgs for this particular path to be received.This defaults to assuming if a peer has been set, its initialized. Can be overridden by the OApp if there is other logic to determine this.", + "params": { + "origin": "The origin information containing the source endpoint and sender address." + }, + "returns": { + "_0": "Whether the path has been initialized." + } + }, + "allowance(address,address)": { + "details": "Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` through {transferFrom}. This is zero by default. This value changes when {approve} or {transferFrom} are called." + }, + "approvalRequired()": { + "details": "In the case of OFT where the contract IS the token, approval is NOT required.", + "returns": { + "_0": "requiresApproval Needs approval of the underlying token implementation." + } + }, + "approve(address,uint256)": { + "details": "See {IERC20-approve}. NOTE: If `value` is the maximum `uint256`, the allowance is not updated on `transferFrom`. This is semantically equivalent to an infinite approval. Requirements: - `spender` cannot be the zero address." + }, + "balanceOf(address)": { + "details": "Returns the value of tokens owned by `account`." + }, + "combineOptions(uint32,uint16,bytes)": { + "details": "If there is an enforced lzReceive option: - {gasLimit: 200k, msg.value: 1 ether} AND a caller supplies a lzReceive option: {gasLimit: 100k, msg.value: 0.5 ether} - The resulting options will be {gasLimit: 300k, msg.value: 1.5 ether} when the message is executed on the remote lzReceive() function.This presence of duplicated options is handled off-chain in the verifier/executor.", + "params": { + "_eid": "The endpoint ID.", + "_extraOptions": "Additional options passed by the caller.", + "_msgType": "The OAPP message type." + }, + "returns": { + "_0": "options The combination of caller specified options AND enforced options." + } + }, + "decimals()": { + "details": "Returns the number of decimals used to get its user representation. For example, if `decimals` equals `2`, a balance of `505` tokens should be displayed to a user as `5.05` (`505 / 10 ** 2`). Tokens usually opt for a value of 18, imitating the relationship between Ether and Wei. This is the default value returned by this function, unless it's overridden. NOTE: This information is only used for _display_ purposes: it in no way affects any of the arithmetic of the contract, including {IERC20-balanceOf} and {IERC20-transfer}." + }, + "isComposeMsgSender((uint32,bytes32,uint64),bytes,address)": { + "details": "_origin The origin information containing the source endpoint and sender address. - srcEid: The source chain endpoint ID. - sender: The sender address on the src chain. - nonce: The nonce of the message._message The lzReceive payload.Applications can optionally choose to implement separate composeMsg senders that are NOT the bridging layer.The default sender IS the OAppReceiver implementer.", + "params": { + "_sender": "The sender address." + }, + "returns": { + "_0": "isSender Is a valid sender." + } + }, + "isPeer(uint32,bytes32)": { + "details": "Check if the peer is considered 'trusted' by the OApp.Enables OAppPreCrimeSimulator to check whether a potential Inbound Packet is from a trusted source.", + "params": { + "_eid": "The endpoint ID to check.", + "_peer": "The peer to check." + }, + "returns": { + "_0": "Whether the peer passed is considered 'trusted' by the OApp." + } + }, + "lzReceive((uint32,bytes32,uint64),bytes32,bytes,address,bytes)": { + "details": "Entry point for receiving messages or packets from the endpoint.Entry point for receiving msg/packet from the LayerZero endpoint.", + "params": { + "_executor": "The address of the executor for the received message.", + "_extraData": "Additional arbitrary data provided by the corresponding executor.", + "_guid": "The unique identifier for the received LayerZero message.", + "_message": "The payload of the received message.", + "_origin": "The origin information containing the source endpoint and sender address. - srcEid: The source chain endpoint ID. - sender: The sender address on the src chain. - nonce: The nonce of the message." + } + }, + "lzReceiveAndRevert(((uint32,bytes32,uint64),uint32,address,bytes32,uint256,address,bytes,bytes)[])": { + "details": "Interface for pre-crime simulations. Always reverts at the end with the simulation results.WARNING: MUST revert at the end with the simulation results.Gives the preCrime implementation the ability to mock sending packets to the lzReceive function, WITHOUT actually executing them.", + "params": { + "_packets": "An array of InboundPacket objects representing received packets to be delivered." + } + }, + "lzReceiveSimulate((uint32,bytes32,uint64),bytes32,bytes,address,bytes)": { + "details": "Is effectively an internal function because msg.sender must be address(this). Allows resetting the call stack for 'internal' calls.", + "params": { + "_executor": "The executor address for the packet.", + "_extraData": "Additional data for the packet.", + "_guid": "The unique identifier of the packet.", + "_message": "The message payload of the packet.", + "_origin": "The origin information containing the source endpoint and sender address. - srcEid: The source chain endpoint ID. - sender: The sender address on the src chain. - nonce: The nonce of the message." + } + }, + "mint(address,uint256)": { + "details": "Deliberately unguarded: the demo mints to itself before each send, and a testnet OFT with no way to obtain tokens cannot be used to exercise the DVN at all. Do NOT ship this to a network where the token has value. `virtual` because MyOFTMock declares the same function for the hardhat tests." + }, + "name()": { + "details": "Returns the name of the token." + }, + "nextNonce(uint32,bytes32)": { + "details": "_srcEid The source endpoint ID._sender The sender address.The path nonce starts from 1. If 0 is returned it means that there is NO nonce ordered enforcement.Is required by the off-chain executor to determine the OApp expects msg execution is ordered.This is also enforced by the OApp.By default this is NOT enabled. ie. nextNonce is hardcoded to return 0.", + "returns": { + "nonce": "The next nonce." + } + }, + "oApp()": { + "details": "Retrieves the address of the OApp contract.The simulator contract is the base contract for the OApp by default.If the simulator is a separate contract, override this function.", + "returns": { + "_0": "The address of the OApp contract." + } + }, + "oAppVersion()": { + "returns": { + "receiverVersion": "The version of the OAppReceiver.sol implementation.", + "senderVersion": "The version of the OAppSender.sol implementation." + } + }, + "oftVersion()": { + "details": "interfaceId: This specific interface ID is '0x02e49c2c'.version: Indicates a cross-chain compatible msg encoding with other OFTs.If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented. ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)", + "returns": { + "interfaceId": "The interface ID.", + "version": "The version." + } + }, + "owner()": { + "details": "Returns the address of the current owner." + }, + "quoteOFT((uint32,bytes32,uint256,uint256,bytes,bytes,bytes))": { + "params": { + "_sendParam": "The parameters for the send operation." + }, + "returns": { + "oftFeeDetails": "The details of OFT fees.", + "oftLimit": "The OFT limit information.", + "oftReceipt": "The OFT receipt information." + } + }, + "quoteSend((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),bool)": { + "details": "MessagingFee: LayerZero msg fee - nativeFee: The native fee. - lzTokenFee: The lzToken fee.", + "params": { + "_payInLzToken": "Flag indicating whether the caller is paying in the LZ token.", + "_sendParam": "The parameters for the send() operation." + }, + "returns": { + "msgFee": "The calculated LayerZero messaging fee from the send() operation." + } + }, + "renounceOwnership()": { + "details": "Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner." + }, + "send((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),(uint256,uint256),address)": { + "details": "Executes the send operation.MessagingReceipt: LayerZero msg receipt - guid: The unique identifier for the sent message. - nonce: The nonce of the sent message. - fee: The LayerZero fee incurred for the message.", + "params": { + "_fee": "The calculated fee for the send() operation. - nativeFee: The native fee. - lzTokenFee: The lzToken fee.", + "_refundAddress": "The address to receive any excess funds.", + "_sendParam": "The parameters for the send operation." + }, + "returns": { + "msgReceipt": "The receipt for the send operation.", + "oftReceipt": "The OFT receipt information." + } + }, + "setDelegate(address)": { + "details": "Only the owner/admin of the OApp can call this function.Provides the ability for a delegate to set configs, on behalf of the OApp, directly on the Endpoint contract.", + "params": { + "_delegate": "The address of the delegate to be set." + } + }, + "setEnforcedOptions((uint32,uint16,bytes)[])": { + "details": "Sets the enforced options for specific endpoint and message type combinations.Only the owner/admin of the OApp can call this function.Provides a way for the OApp to enforce things like paying for PreCrime, AND/OR minimum dst lzReceive gas amounts etc.These enforced options can vary as the potential options/execution on the remote may differ as per the msgType. eg. Amount of lzReceive() gas necessary to deliver a lzCompose() message adds overhead you dont want to pay if you are only making a standard LayerZero message ie. lzReceive() WITHOUT sendCompose().", + "params": { + "_enforcedOptions": "An array of EnforcedOptionParam structures specifying enforced options." + } + }, + "setMsgInspector(address)": { + "details": "Sets the message inspector address for the OFT.This is an optional contract that can be used to inspect both 'message' and 'options'.Set it to address(0) to disable it, or set it to a contract address to enable it.", + "params": { + "_msgInspector": "The address of the message inspector." + } + }, + "setPeer(uint32,bytes32)": { + "details": "Only the owner/admin of the OApp can call this function.Indicates that the peer is trusted to send LayerZero messages to this OApp.Set this to bytes32(0) to remove the peer address.Peer is a bytes32 to accommodate non-evm chains.", + "params": { + "_eid": "The endpoint ID.", + "_peer": "The address of the peer to be associated with the corresponding endpoint." + } + }, + "setPreCrime(address)": { + "details": "Sets the preCrime contract address.", + "params": { + "_preCrime": "The address of the preCrime contract." + } + }, + "sharedDecimals()": { + "details": "Retrieves the shared decimals of the OFT.Sets an implicit cap on the amount of tokens, over uint64.max() will need some sort of outbound cap / totalSupply cap Lowest common decimal denominator between chains. Defaults to 6 decimal places to provide up to 18,446,744,073,709.551615 units (max uint64). For tokens exceeding this totalSupply(), they will need to override the sharedDecimals function with something smaller. ie. 4 sharedDecimals would be 1,844,674,407,370,955.1615", + "returns": { + "_0": "The shared decimals of the OFT." + } + }, + "symbol()": { + "details": "Returns the symbol of the token, usually a shorter version of the name." + }, + "token()": { + "details": "Retrieves the address of the underlying ERC20 implementation.In the case of OFT, address(this) and erc20 are the same contract.", + "returns": { + "_0": "The address of the OFT token." + } + }, + "totalSupply()": { + "details": "Returns the value of tokens in existence." + }, + "transfer(address,uint256)": { + "details": "See {IERC20-transfer}. Requirements: - `to` cannot be the zero address. - the caller must have a balance of at least `value`." + }, + "transferFrom(address,address,uint256)": { + "details": "See {IERC20-transferFrom}. Skips emitting an {Approval} event indicating an allowance update. This is not required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve]. NOTE: Does not update the allowance if the current allowance is the maximum `uint256`. Requirements: - `from` and `to` cannot be the zero address. - `from` must have a balance of at least `value`. - the caller must have allowance for ``from``'s tokens of at least `value`." + }, + "transferOwnership(address)": { + "details": "Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner." + } + }, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "allowInitializePath((uint32,bytes32,uint64))": { + "notice": "Checks if the path initialization is allowed based on the provided origin." + }, + "approvalRequired()": { + "notice": "Indicates whether the OFT contract requires approval of the 'token()' to send." + }, + "combineOptions(uint32,uint16,bytes)": { + "notice": "Combines options for a given endpoint and message type." + }, + "endpoint()": { + "notice": "Retrieves the LayerZero endpoint associated with the OApp." + }, + "isComposeMsgSender((uint32,bytes32,uint64),bytes,address)": { + "notice": "Indicates whether an address is an approved composeMsg sender to the Endpoint." + }, + "mint(address,uint256)": { + "notice": "Open mint, TESTNET ONLY — same as ToyOFT, so the demo tasks work against either." + }, + "nextNonce(uint32,bytes32)": { + "notice": "Retrieves the next nonce for a given source endpoint and sender address." + }, + "oAppVersion()": { + "notice": "Retrieves the OApp version information." + }, + "oftVersion()": { + "notice": "Retrieves interfaceID and the version of the OFT." + }, + "peers(uint32)": { + "notice": "Retrieves the peer (OApp) associated with a corresponding endpoint." + }, + "quoteOFT((uint32,bytes32,uint256,uint256,bytes,bytes,bytes))": { + "notice": "Provides the fee breakdown and settings data for an OFT. Unused in the default implementation." + }, + "quoteSend((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),bool)": { + "notice": "Provides a quote for the send() operation." + }, + "setDelegate(address)": { + "notice": "Sets the delegate address for the OApp." + }, + "setPeer(uint32,bytes32)": { + "notice": "Sets the peer address (OApp instance) for a corresponding endpoint." + } + }, + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 3861, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "t_address" + }, + { + "astId": 1390, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "peers", + "offset": 0, + "slot": "1", + "type": "t_mapping(t_uint32,t_bytes32)" + }, + { + "astId": 2166, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "preCrime", + "offset": 0, + "slot": "2", + "type": "t_address" + }, + { + "astId": 2006, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "enforcedOptions", + "offset": 0, + "slot": "3", + "type": "t_mapping(t_uint32,t_mapping(t_uint16,t_bytes_storage))" + }, + { + "astId": 2786, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "msgInspector", + "offset": 0, + "slot": "4", + "type": "t_address" + }, + { + "astId": 4250, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "_balances", + "offset": 0, + "slot": "5", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 4256, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "_allowances", + "offset": 0, + "slot": "6", + "type": "t_mapping(t_address,t_mapping(t_address,t_uint256))" + }, + { + "astId": 4258, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "_totalSupply", + "offset": 0, + "slot": "7", + "type": "t_uint256" + }, + { + "astId": 4260, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "_name", + "offset": 0, + "slot": "8", + "type": "t_string_storage" + }, + { + "astId": 4262, + "contract": "contracts/MyOFT.sol:MyOFT", + "label": "_symbol", + "offset": 0, + "slot": "9", + "type": "t_string_storage" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_bytes32": { + "encoding": "inplace", + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_bytes_storage": { + "encoding": "bytes", + "label": "bytes", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32", + "value": "t_mapping(t_address,t_uint256)" + }, + "t_mapping(t_address,t_uint256)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_mapping(t_uint16,t_bytes_storage)": { + "encoding": "mapping", + "key": "t_uint16", + "label": "mapping(uint16 => bytes)", + "numberOfBytes": "32", + "value": "t_bytes_storage" + }, + "t_mapping(t_uint32,t_bytes32)": { + "encoding": "mapping", + "key": "t_uint32", + "label": "mapping(uint32 => bytes32)", + "numberOfBytes": "32", + "value": "t_bytes32" + }, + "t_mapping(t_uint32,t_mapping(t_uint16,t_bytes_storage))": { + "encoding": "mapping", + "key": "t_uint32", + "label": "mapping(uint32 => mapping(uint16 => bytes))", + "numberOfBytes": "32", + "value": "t_mapping(t_uint16,t_bytes_storage)" + }, + "t_string_storage": { + "encoding": "bytes", + "label": "string", + "numberOfBytes": "32" + }, + "t_uint16": { + "encoding": "inplace", + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint32": { + "encoding": "inplace", + "label": "uint32", + "numberOfBytes": "4" + } + } + } +} \ No newline at end of file diff --git a/deployments/optimism-sepolia/RiskyProxyMock.json b/deployments/optimism-sepolia/RiskyProxyMock.json new file mode 100644 index 0000000..1d2620b --- /dev/null +++ b/deployments/optimism-sepolia/RiskyProxyMock.json @@ -0,0 +1,111 @@ +{ + "address": "0x9771013D82dcC2bdb489B982B4f201FD698A15e6", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_admin", + "type": "address" + }, + { + "internalType": "address", + "name": "_implementation", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address", + "name": "a", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "i", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "transactionHash": "0x7170b2535febe3975d6f3061bf387e415eb83e9d68fe4a934ca8bda3072ee939", + "receipt": { + "to": null, + "from": "0x8583894d0e57e42abb83039537f314490038efa0", + "contractAddress": "0x9771013D82dcC2bdb489B982B4f201FD698A15e6", + "transactionIndex": 2, + "gasUsed": "149244", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xf4edef6332f6f0933fc9d43cc8368ffbc5ac4feea47307721a9f9dc46790376a", + "transactionHash": "0x7170b2535febe3975d6f3061bf387e415eb83e9d68fe4a934ca8bda3072ee939", + "logs": [], + "blockNumber": 46865188, + "cumulativeGasUsed": "319346", + "status": 1, + "byzantium": true + }, + "args": [ + "0x000000000000000000000000000000000000dEaD", + "0x8583894d0e57e42abb83039537f314490038efa0" + ], + "numDeployments": 1, + "solcInputHash": "91f67dad93f3438967ecce8dd1aaa287", + "metadata": "{\"compiler\":{\"version\":\"0.8.22+commit.4fc1097e\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_admin\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_implementation\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"admin\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"a\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"implementation\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"i\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"details\":\"The engine reads the two EIP-1967 slots directly (see `assess/providers/contract.ts`): an implementation slot that is set means the code behind this address can change, and the admin slot names whoever can change it. It then looks that admin up in the risk store \\u2014 a flagged admin is the signal, because today's clean code says nothing about tomorrow's if a sanctioned party can swap it out. The slots are written straight to storage rather than by deploying a real proxy: what is being demonstrated is the engine's reading of them, and a forwarding proxy would add a delegatecall path with nothing to delegate to.\",\"kind\":\"dev\",\"methods\":{\"constructor\":{\"params\":{\"_admin\":\"The address to present as able to upgrade this contract. Point it at an address the operator has flagged (e.g. one in TEST_DENYLIST) for the check to fire.\",\"_implementation\":\"Any non-zero address; its only job is to make the proxy slot set.\"}}},\"stateVariables\":{\"SLOT_ADMIN\":{\"details\":\"keccak256(\\\"eip1967.proxy.admin\\\") - 1\"},\"SLOT_IMPLEMENTATION\":{\"details\":\"keccak256(\\\"eip1967.proxy.implementation\\\") - 1\"}},\"title\":\"RiskyProxyMock\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"admin()\":{\"notice\":\"The admin as stored in the EIP-1967 slot, for anyone reading it the easy way.\"},\"implementation()\":{\"notice\":\"The implementation as stored in the EIP-1967 slot.\"}},\"notice\":\"A testnet decoy that looks like an upgradeable proxy controlled by a flagged address, for exercising the risk engine's `contract_admin_risk` check.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/mocks/RiskyProxyMock.sol\":\"RiskyProxyMock\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"contracts/mocks/RiskyProxyMock.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.22;\\n\\n/// @title RiskyProxyMock\\n/// @notice A testnet decoy that looks like an upgradeable proxy controlled by a flagged address,\\n/// for exercising the risk engine's `contract_admin_risk` check.\\n/// @dev The engine reads the two EIP-1967 slots directly (see `assess/providers/contract.ts`):\\n/// an implementation slot that is set means the code behind this address can change, and the\\n/// admin slot names whoever can change it. It then looks that admin up in the risk store \\u2014\\n/// a flagged admin is the signal, because today's clean code says nothing about tomorrow's\\n/// if a sanctioned party can swap it out.\\n///\\n/// The slots are written straight to storage rather than by deploying a real proxy: what is\\n/// being demonstrated is the engine's reading of them, and a forwarding proxy would add a\\n/// delegatecall path with nothing to delegate to.\\ncontract RiskyProxyMock {\\n /// @dev keccak256(\\\"eip1967.proxy.implementation\\\") - 1\\n bytes32 private constant SLOT_IMPLEMENTATION =\\n 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\\n /// @dev keccak256(\\\"eip1967.proxy.admin\\\") - 1\\n bytes32 private constant SLOT_ADMIN = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;\\n\\n /// @param _admin The address to present as able to upgrade this contract. Point it at an\\n /// address the operator has flagged (e.g. one in TEST_DENYLIST) for the check to fire.\\n /// @param _implementation Any non-zero address; its only job is to make the proxy slot set.\\n constructor(address _admin, address _implementation) {\\n require(_admin != address(0), \\\"zero admin\\\");\\n require(_implementation != address(0), \\\"zero implementation\\\");\\n assembly {\\n sstore(SLOT_ADMIN, _admin)\\n sstore(SLOT_IMPLEMENTATION, _implementation)\\n }\\n }\\n\\n /// @notice The admin as stored in the EIP-1967 slot, for anyone reading it the easy way.\\n function admin() external view returns (address a) {\\n assembly {\\n a := sload(SLOT_ADMIN)\\n }\\n }\\n\\n /// @notice The implementation as stored in the EIP-1967 slot.\\n function implementation() external view returns (address i) {\\n assembly {\\n i := sload(SLOT_IMPLEMENTATION)\\n }\\n }\\n}\\n\",\"keccak256\":\"0xf54c10753cd83e2a3f3b58cac49ba5a37eb0314ff51853ce443fc5917cc42f03\",\"license\":\"MIT\"}},\"version\":1}", + "bytecode": "0x608060405234801561001057600080fd5b5060405161024838038061024883398101604081905261002f91610135565b6001600160a01b0382166100775760405162461bcd60e51b815260206004820152600a6024820152693d32b9379030b236b4b760b11b60448201526064015b60405180910390fd5b6001600160a01b0381166100cd5760405162461bcd60e51b815260206004820152601360248201527f7a65726f20696d706c656d656e746174696f6e00000000000000000000000000604482015260640161006e565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103919091557f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc55610168565b80516001600160a01b038116811461013057600080fd5b919050565b6000806040838503121561014857600080fd5b61015183610119565b915061015f60208401610119565b90509250929050565b60d2806101766000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c80635c60da1b146037578063f851a440146076575b600080fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545b6040516001600160a01b03909116815260200160405180910390f35b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610354605a56fea2646970667358221220db4d640c5bd6b171ede814a8f22af946c7a8d98b7ba3fb5d90d436509788860864736f6c63430008160033", + "deployedBytecode": "0x6080604052348015600f57600080fd5b506004361060325760003560e01c80635c60da1b146037578063f851a440146076575b600080fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545b6040516001600160a01b03909116815260200160405180910390f35b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610354605a56fea2646970667358221220db4d640c5bd6b171ede814a8f22af946c7a8d98b7ba3fb5d90d436509788860864736f6c63430008160033", + "devdoc": { + "details": "The engine reads the two EIP-1967 slots directly (see `assess/providers/contract.ts`): an implementation slot that is set means the code behind this address can change, and the admin slot names whoever can change it. It then looks that admin up in the risk store — a flagged admin is the signal, because today's clean code says nothing about tomorrow's if a sanctioned party can swap it out. The slots are written straight to storage rather than by deploying a real proxy: what is being demonstrated is the engine's reading of them, and a forwarding proxy would add a delegatecall path with nothing to delegate to.", + "kind": "dev", + "methods": { + "constructor": { + "params": { + "_admin": "The address to present as able to upgrade this contract. Point it at an address the operator has flagged (e.g. one in TEST_DENYLIST) for the check to fire.", + "_implementation": "Any non-zero address; its only job is to make the proxy slot set." + } + } + }, + "stateVariables": { + "SLOT_ADMIN": { + "details": "keccak256(\"eip1967.proxy.admin\") - 1" + }, + "SLOT_IMPLEMENTATION": { + "details": "keccak256(\"eip1967.proxy.implementation\") - 1" + } + }, + "title": "RiskyProxyMock", + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "admin()": { + "notice": "The admin as stored in the EIP-1967 slot, for anyone reading it the easy way." + }, + "implementation()": { + "notice": "The implementation as stored in the EIP-1967 slot." + } + }, + "notice": "A testnet decoy that looks like an upgradeable proxy controlled by a flagged address, for exercising the risk engine's `contract_admin_risk` check.", + "version": 1 + }, + "storageLayout": { + "storage": [], + "types": null + } +} \ No newline at end of file diff --git a/deployments/optimism-sepolia/solcInputs/31caad50e704c80e4a3252d0a262b59d.json b/deployments/optimism-sepolia/solcInputs/31caad50e704c80e4a3252d0a262b59d.json new file mode 100644 index 0000000..7368160 --- /dev/null +++ b/deployments/optimism-sepolia/solcInputs/31caad50e704c80e4a3252d0a262b59d.json @@ -0,0 +1,51 @@ +{ + "language": "Solidity", + "sources": { + "@openzeppelin/contracts/interfaces/draft-IERC6093.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.5.0) (interfaces/draft-IERC6093.sol)\n\npragma solidity >=0.8.4;\n\n/**\n * @dev Standard ERC-20 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-20 tokens.\n */\ninterface IERC20Errors {\n /**\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n * @param balance Current balance for the interacting account.\n * @param needed Minimum amount required to perform a transfer.\n */\n error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);\n\n /**\n * @dev Indicates a failure with the token `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n */\n error ERC20InvalidSender(address sender);\n\n /**\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\n * @param receiver Address to which tokens are being transferred.\n */\n error ERC20InvalidReceiver(address receiver);\n\n /**\n * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.\n * @param spender Address that may be allowed to operate on tokens without being their owner.\n * @param allowance Amount of tokens a `spender` is allowed to operate with.\n * @param needed Minimum amount required to perform a transfer.\n */\n error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);\n\n /**\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n * @param approver Address initiating an approval operation.\n */\n error ERC20InvalidApprover(address approver);\n\n /**\n * @dev Indicates a failure with the `spender` to be approved. Used in approvals.\n * @param spender Address that may be allowed to operate on tokens without being their owner.\n */\n error ERC20InvalidSpender(address spender);\n}\n\n/**\n * @dev Standard ERC-721 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-721 tokens.\n */\ninterface IERC721Errors {\n /**\n * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-721.\n * Used in balance queries.\n * @param owner Address of the current owner of a token.\n */\n error ERC721InvalidOwner(address owner);\n\n /**\n * @dev Indicates a `tokenId` whose `owner` is the zero address.\n * @param tokenId Identifier number of a token.\n */\n error ERC721NonexistentToken(uint256 tokenId);\n\n /**\n * @dev Indicates an error related to the ownership over a particular token. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n * @param tokenId Identifier number of a token.\n * @param owner Address of the current owner of a token.\n */\n error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);\n\n /**\n * @dev Indicates a failure with the token `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n */\n error ERC721InvalidSender(address sender);\n\n /**\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\n * @param receiver Address to which tokens are being transferred.\n */\n error ERC721InvalidReceiver(address receiver);\n\n /**\n * @dev Indicates a failure with the `operator`’s approval. Used in transfers.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n * @param tokenId Identifier number of a token.\n */\n error ERC721InsufficientApproval(address operator, uint256 tokenId);\n\n /**\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n * @param approver Address initiating an approval operation.\n */\n error ERC721InvalidApprover(address approver);\n\n /**\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n */\n error ERC721InvalidOperator(address operator);\n}\n\n/**\n * @dev Standard ERC-1155 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-1155 tokens.\n */\ninterface IERC1155Errors {\n /**\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n * @param balance Current balance for the interacting account.\n * @param needed Minimum amount required to perform a transfer.\n * @param tokenId Identifier number of a token.\n */\n error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);\n\n /**\n * @dev Indicates a failure with the token `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n */\n error ERC1155InvalidSender(address sender);\n\n /**\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\n * @param receiver Address to which tokens are being transferred.\n */\n error ERC1155InvalidReceiver(address receiver);\n\n /**\n * @dev Indicates a failure with the `operator`’s approval. Used in transfers.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n * @param owner Address of the current owner of a token.\n */\n error ERC1155MissingApprovalForAll(address operator, address owner);\n\n /**\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n * @param approver Address initiating an approval operation.\n */\n error ERC1155InvalidApprover(address approver);\n\n /**\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n */\n error ERC1155InvalidOperator(address operator);\n\n /**\n * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.\n * Used in batch transfers.\n * @param idsLength Length of the array of token identifiers\n * @param valuesLength Length of the array of token amounts\n */\n error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/ERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.5.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"./IERC20.sol\";\nimport {IERC20Metadata} from \"./extensions/IERC20Metadata.sol\";\nimport {Context} from \"../../utils/Context.sol\";\nimport {IERC20Errors} from \"../../interfaces/draft-IERC6093.sol\";\n\n/**\n * @dev Implementation of the {IERC20} interface.\n *\n * This implementation is agnostic to the way tokens are created. This means\n * that a supply mechanism has to be added in a derived contract using {_mint}.\n *\n * TIP: For a detailed writeup see our guide\n * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How\n * to implement supply mechanisms].\n *\n * The default value of {decimals} is 18. To change this, you should override\n * this function so it returns a different value.\n *\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\n * instead returning `false` on failure. This behavior is nonetheless\n * conventional and does not conflict with the expectations of ERC-20\n * applications.\n */\nabstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {\n mapping(address account => uint256) private _balances;\n\n mapping(address account => mapping(address spender => uint256)) private _allowances;\n\n uint256 private _totalSupply;\n\n string private _name;\n string private _symbol;\n\n /**\n * @dev Sets the values for {name} and {symbol}.\n *\n * Both values are immutable: they can only be set once during construction.\n */\n constructor(string memory name_, string memory symbol_) {\n _name = name_;\n _symbol = symbol_;\n }\n\n /**\n * @dev Returns the name of the token.\n */\n function name() public view virtual returns (string memory) {\n return _name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view virtual returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei. This is the default value returned by this function, unless\n * it's overridden.\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view virtual returns (uint8) {\n return 18;\n }\n\n /// @inheritdoc IERC20\n function totalSupply() public view virtual returns (uint256) {\n return _totalSupply;\n }\n\n /// @inheritdoc IERC20\n function balanceOf(address account) public view virtual returns (uint256) {\n return _balances[account];\n }\n\n /**\n * @dev See {IERC20-transfer}.\n *\n * Requirements:\n *\n * - `to` cannot be the zero address.\n * - the caller must have a balance of at least `value`.\n */\n function transfer(address to, uint256 value) public virtual returns (bool) {\n address owner = _msgSender();\n _transfer(owner, to, value);\n return true;\n }\n\n /// @inheritdoc IERC20\n function allowance(address owner, address spender) public view virtual returns (uint256) {\n return _allowances[owner][spender];\n }\n\n /**\n * @dev See {IERC20-approve}.\n *\n * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on\n * `transferFrom`. This is semantically equivalent to an infinite approval.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function approve(address spender, uint256 value) public virtual returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, value);\n return true;\n }\n\n /**\n * @dev See {IERC20-transferFrom}.\n *\n * Skips emitting an {Approval} event indicating an allowance update. This is not\n * required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].\n *\n * NOTE: Does not update the allowance if the current allowance\n * is the maximum `uint256`.\n *\n * Requirements:\n *\n * - `from` and `to` cannot be the zero address.\n * - `from` must have a balance of at least `value`.\n * - the caller must have allowance for ``from``'s tokens of at least\n * `value`.\n */\n function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {\n address spender = _msgSender();\n _spendAllowance(from, spender, value);\n _transfer(from, to, value);\n return true;\n }\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to`.\n *\n * This internal function is equivalent to {transfer}, and can be used to\n * e.g. implement automatic token fees, slashing mechanisms, etc.\n *\n * Emits a {Transfer} event.\n *\n * NOTE: This function is not virtual, {_update} should be overridden instead.\n */\n function _transfer(address from, address to, uint256 value) internal {\n if (from == address(0)) {\n revert ERC20InvalidSender(address(0));\n }\n if (to == address(0)) {\n revert ERC20InvalidReceiver(address(0));\n }\n _update(from, to, value);\n }\n\n /**\n * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`\n * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding\n * this function.\n *\n * Emits a {Transfer} event.\n */\n function _update(address from, address to, uint256 value) internal virtual {\n if (from == address(0)) {\n // Overflow check required: The rest of the code assumes that totalSupply never overflows\n _totalSupply += value;\n } else {\n uint256 fromBalance = _balances[from];\n if (fromBalance < value) {\n revert ERC20InsufficientBalance(from, fromBalance, value);\n }\n unchecked {\n // Overflow not possible: value <= fromBalance <= totalSupply.\n _balances[from] = fromBalance - value;\n }\n }\n\n if (to == address(0)) {\n unchecked {\n // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.\n _totalSupply -= value;\n }\n } else {\n unchecked {\n // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.\n _balances[to] += value;\n }\n }\n\n emit Transfer(from, to, value);\n }\n\n /**\n * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).\n * Relies on the `_update` mechanism\n *\n * Emits a {Transfer} event with `from` set to the zero address.\n *\n * NOTE: This function is not virtual, {_update} should be overridden instead.\n */\n function _mint(address account, uint256 value) internal {\n if (account == address(0)) {\n revert ERC20InvalidReceiver(address(0));\n }\n _update(address(0), account, value);\n }\n\n /**\n * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.\n * Relies on the `_update` mechanism.\n *\n * Emits a {Transfer} event with `to` set to the zero address.\n *\n * NOTE: This function is not virtual, {_update} should be overridden instead\n */\n function _burn(address account, uint256 value) internal {\n if (account == address(0)) {\n revert ERC20InvalidSender(address(0));\n }\n _update(account, address(0), value);\n }\n\n /**\n * @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.\n *\n * This internal function is equivalent to `approve`, and can be used to\n * e.g. set automatic allowances for certain subsystems, etc.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `owner` cannot be the zero address.\n * - `spender` cannot be the zero address.\n *\n * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.\n */\n function _approve(address owner, address spender, uint256 value) internal {\n _approve(owner, spender, value, true);\n }\n\n /**\n * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.\n *\n * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by\n * `_spendAllowance` during the `transferFrom` operation sets the flag to false. This saves gas by not emitting any\n * `Approval` event during `transferFrom` operations.\n *\n * Anyone who wishes to continue emitting `Approval` events on the `transferFrom` operation can force the flag to\n * true using the following override:\n *\n * ```solidity\n * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {\n * super._approve(owner, spender, value, true);\n * }\n * ```\n *\n * Requirements are the same as {_approve}.\n */\n function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {\n if (owner == address(0)) {\n revert ERC20InvalidApprover(address(0));\n }\n if (spender == address(0)) {\n revert ERC20InvalidSpender(address(0));\n }\n _allowances[owner][spender] = value;\n if (emitEvent) {\n emit Approval(owner, spender, value);\n }\n }\n\n /**\n * @dev Updates `owner`'s allowance for `spender` based on spent `value`.\n *\n * Does not update the allowance value in case of infinite allowance.\n * Revert if not enough allowance is available.\n *\n * Does not emit an {Approval} event.\n */\n function _spendAllowance(address owner, address spender, uint256 value) internal virtual {\n uint256 currentAllowance = allowance(owner, spender);\n if (currentAllowance < type(uint256).max) {\n if (currentAllowance < value) {\n revert ERC20InsufficientAllowance(spender, currentAllowance, value);\n }\n unchecked {\n _approve(owner, spender, currentAllowance - value, false);\n }\n }\n }\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/extensions/IERC20Metadata.sol)\n\npragma solidity >=0.6.2;\n\nimport {IERC20} from \"../IERC20.sol\";\n\n/**\n * @dev Interface for the optional metadata functions from the ERC-20 standard.\n */\ninterface IERC20Metadata is IERC20 {\n /**\n * @dev Returns the name of the token.\n */\n function name() external view returns (string memory);\n\n /**\n * @dev Returns the symbol of the token.\n */\n function symbol() external view returns (string memory);\n\n /**\n * @dev Returns the decimals places of the token.\n */\n function decimals() external view returns (uint8);\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)\n\npragma solidity >=0.4.16;\n\n/**\n * @dev Interface of the ERC-20 standard as defined in the ERC.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the value of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the value of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves a `value` amount of tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 value) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n * caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 value) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to` using the\n * allowance mechanism. `value` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address from, address to, uint256 value) external returns (bool);\n}\n" + }, + "@openzeppelin/contracts/utils/Context.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n function _contextSuffixLength() internal view virtual returns (uint256) {\n return 0;\n }\n}\n" + }, + "contracts/mocks/FakeStablecoinMock.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.22;\n\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\n/// @title FakeStablecoinMock\n/// @notice A testnet decoy that claims to be USDC, for exercising the risk engine's\n/// impersonation check. It is NOT a stablecoin and holds no value.\n/// @dev The engine's token screening resolves a subject's underlying token through `token()`,\n/// reads `symbol()`/`decimals()`, and compares the address against the chain's canonical\n/// issuer (see `CANONICAL_STABLECOINS` in worker/assess/providers/token.ts). Claiming a\n/// watched symbol from a non-canonical address is exactly the pattern\n/// `fake_stablecoin_suspect` exists to catch — so this contract asserts the symbol and\n/// nothing else. Deploy only to testnets.\ncontract FakeStablecoinMock is ERC20 {\n constructor() ERC20(\"USD Coin\", \"USDC\") {}\n\n /// @dev Six, like the real thing: the check is about the address, and matching the decimals\n /// keeps the decoy from being dismissed on a detail the engine does not rely on.\n function decimals() public pure override returns (uint8) {\n return 6;\n }\n\n /// @notice Reports itself as its own underlying token.\n /// @dev This is what makes the engine treat the address as a token rather than a plain OApp:\n /// `resolveToken` calls `token()` and screens whatever address comes back.\n function token() external view returns (address) {\n return address(this);\n }\n\n /// @notice Open mint, testnet only — a decoy with no supply is harder to look at in an explorer.\n function mint(address _to, uint256 _amount) external {\n _mint(_to, _amount);\n }\n}\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "evmVersion": "paris", + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + "devdoc", + "userdoc", + "storageLayout", + "evm.gasEstimates" + ], + "": [ + "ast" + ] + } + }, + "metadata": { + "useLiteralContent": true + } + } +} \ No newline at end of file diff --git a/deployments/optimism-sepolia/solcInputs/557c64b7ef69b127f89738ff42782948.json b/deployments/optimism-sepolia/solcInputs/557c64b7ef69b127f89738ff42782948.json new file mode 100644 index 0000000..3aa2108 --- /dev/null +++ b/deployments/optimism-sepolia/solcInputs/557c64b7ef69b127f89738ff42782948.json @@ -0,0 +1,51 @@ +{ + "language": "Solidity", + "sources": { + "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/ILayerZeroDVN.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\ninterface ILayerZeroDVN {\n struct AssignJobParam {\n uint32 dstEid;\n bytes packetHeader;\n bytes32 payloadHash;\n uint64 confirmations;\n address sender;\n }\n\n // @notice query price and assign jobs at the same time\n // @param _dstEid - the destination endpoint identifier\n // @param _packetHeader - version + nonce + path\n // @param _payloadHash - hash of guid + message\n // @param _confirmations - block confirmation delay before relaying blocks\n // @param _sender - the source sending contract address\n // @param _options - options\n function assignJob(AssignJobParam calldata _param, bytes calldata _options) external payable returns (uint256 fee);\n\n // @notice query the dvn fee for relaying block information to the destination chain\n // @param _dstEid the destination endpoint identifier\n // @param _confirmations - block confirmation delay before relaying blocks\n // @param _sender - the source sending contract address\n // @param _options - options\n function getFee(\n uint32 _dstEid,\n uint64 _confirmations,\n address _sender,\n bytes calldata _options\n ) external view returns (uint256 fee);\n}\n" + }, + "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/IReceiveUlnE2.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\n/// @dev should be implemented by the ReceiveUln302 contract and future ReceiveUln contracts on EndpointV2\ninterface IReceiveUlnE2 {\n /// @notice for each dvn to verify the payload\n /// @dev this function signature 0x0223536e\n function verify(bytes calldata _packetHeader, bytes32 _payloadHash, uint64 _confirmations) external;\n\n /// @notice verify the payload at endpoint, will check if all DVNs verified\n function commitVerification(bytes calldata _packetHeader, bytes32 _payloadHash) external;\n}\n" + }, + "@openzeppelin/contracts/access/Ownable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\n\npragma solidity ^0.8.20;\n\nimport {Context} from \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * The initial owner is set to the address provided by the deployer. This can\n * later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n /**\n * @dev The caller account is not authorized to perform an operation.\n */\n error OwnableUnauthorizedAccount(address account);\n\n /**\n * @dev The owner is not a valid owner account. (eg. `address(0)`)\n */\n error OwnableInvalidOwner(address owner);\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\n */\n constructor(address initialOwner) {\n if (initialOwner == address(0)) {\n revert OwnableInvalidOwner(address(0));\n }\n _transferOwnership(initialOwner);\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n if (owner() != _msgSender()) {\n revert OwnableUnauthorizedAccount(_msgSender());\n }\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby disabling any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n if (newOwner == address(0)) {\n revert OwnableInvalidOwner(address(0));\n }\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n" + }, + "@openzeppelin/contracts/utils/Context.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n function _contextSuffixLength() internal view virtual returns (uint256) {\n return 0;\n }\n}\n" + }, + "contracts/ComplianceDVN.sol": { + "content": "// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.22;\r\n\r\nimport { Ownable } from \"@openzeppelin/contracts/access/Ownable.sol\";\r\nimport { ILayerZeroDVN } from \"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/ILayerZeroDVN.sol\";\r\nimport { IReceiveUlnE2 } from \"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/IReceiveUlnE2.sol\";\r\n\r\n/// @title ComplianceDVN\r\n/// @notice Thin LayerZero V2 DVN. All compliance judgment is off-chain; the on-chain\r\n/// contract only conforms to the worker-job interface and gates the destination\r\n/// attestation behind an operator key. Withholding `submitVerification` IS the veto.\r\ncontract ComplianceDVN is ILayerZeroDVN, Ownable {\r\n address public operator; // off-chain worker key\r\n address public receiveUln; // ReceiveUln302 on this chain\r\n uint256 public fee;\r\n\r\n event JobAssigned(uint32 dstEid, bytes32 payloadHash, uint64 confirmations, address sender);\r\n event OperatorSet(address operator);\r\n event ReceiveUlnSet(address receiveUln);\r\n event FeeSet(uint256 fee);\r\n\r\n /// @notice A held packet cleared for verification by the owner. Deliberately owner-only:\r\n /// the worker holds only the operator key, so it cannot approve its own holds.\r\n event PacketApproved(bytes32 indexed payloadHash, address approver);\r\n\r\n /// @notice The risk decision behind a packet's outcome.\r\n /// @param payloadHash the packet this verdict is about\r\n /// @param action ACTION_* below\r\n /// @param score 0-100 risk score the action was derived from\r\n /// @param reasonMask bitmask of reason codes; bit assignments are append-only and\r\n /// documented in the worker's `assess/verdict.ts`\r\n /// @param evidenceHash keccak256 of the canonical evidence document held off-chain\r\n event RiskVerdict(\r\n bytes32 indexed payloadHash,\r\n uint8 action,\r\n uint16 score,\r\n uint256 reasonMask,\r\n bytes32 evidenceHash\r\n );\r\n\r\n /// @dev Action codes. These are part of the event ABI: an indexer decoding old logs relies\r\n /// on them, so the numbering is permanent. Kept in sync with the worker's ACTION_CODES.\r\n uint8 public constant ACTION_ALLOW = 0;\r\n uint8 public constant ACTION_DELAY = 1;\r\n uint8 public constant ACTION_MANUAL_REVIEW = 2;\r\n uint8 public constant ACTION_BLOCK = 3;\r\n\r\n error NotOperator();\r\n error UnknownAction(uint8 action);\r\n /// @dev Submitting a verification asserts the packet was allowed; any other action would be\r\n /// a self-contradicting record.\r\n error VerificationRequiresAllow(uint8 action);\r\n /// @dev An allow rides along on `submitVerification`, so recording one separately would\r\n /// double-report the same outcome.\r\n error AllowNotSeparatelyRecorded();\r\n\r\n modifier onlyOperator() {\r\n if (msg.sender != operator) revert NotOperator();\r\n _;\r\n }\r\n\r\n constructor(address _owner, address _operator, address _receiveUln, uint256 _fee) Ownable(_owner) {\r\n require(_operator != address(0), \"zero operator\");\r\n require(_receiveUln != address(0), \"zero receiveUln\");\r\n operator = _operator;\r\n receiveUln = _receiveUln;\r\n fee = _fee;\r\n }\r\n\r\n function getFee(\r\n uint32 /*_dstEid*/,\r\n uint64 /*_confirmations*/,\r\n address /*_sender*/,\r\n bytes calldata /*_options*/\r\n ) external view returns (uint256) {\r\n return fee;\r\n }\r\n\r\n function assignJob(AssignJobParam calldata _param, bytes calldata) external payable returns (uint256) {\r\n // NOTE: SendUln302 calls assignJob WITHOUT forwarding value (msg.value == 0); the\r\n // messagelib accrues each worker's fee internally and workers withdraw separately\r\n // (see SendUlnBase._assignJobs). So we must NOT require msg.value >= fee here — doing\r\n // so reverts every real send. We simply record the job and return our fee quote.\r\n emit JobAssigned(_param.dstEid, _param.payloadHash, _param.confirmations, _param.sender);\r\n return fee;\r\n }\r\n\r\n /// @notice Attest a packet and record the risk verdict that permitted it, in one call.\r\n /// @dev The verdict rides along at no extra transaction cost, so an allowed packet always\r\n /// carries an auditable reason for having been allowed. `action` must be ACTION_ALLOW:\r\n /// a packet that was blocked or held cannot also have been verified. An owner-approved\r\n /// release is reported as ACTION_ALLOW too — a human allowed it — with the reason mask\r\n /// still carrying why it had been held.\r\n function submitVerification(\r\n bytes calldata packetHeader,\r\n bytes32 payloadHash,\r\n uint64 confirmations,\r\n uint8 action,\r\n uint16 score,\r\n uint256 reasonMask,\r\n bytes32 evidenceHash\r\n ) external onlyOperator {\r\n if (action != ACTION_ALLOW) revert VerificationRequiresAllow(action);\r\n IReceiveUlnE2(receiveUln).verify(packetHeader, payloadHash, confirmations);\r\n emit RiskVerdict(payloadHash, action, score, reasonMask, evidenceHash);\r\n }\r\n\r\n /// @notice Record a verdict for a packet that was NOT verified.\r\n /// @dev Withholding the attestation is what actually stops the packet; this only leaves the\r\n /// audit trail. It is therefore best-effort by design — the worker treats a failure\r\n /// here as a lost record, never as a failure to enforce.\r\n function recordVerdict(\r\n bytes32 payloadHash,\r\n uint8 action,\r\n uint16 score,\r\n uint256 reasonMask,\r\n bytes32 evidenceHash\r\n ) external onlyOperator {\r\n if (action > ACTION_BLOCK) revert UnknownAction(action);\r\n if (action == ACTION_ALLOW) revert AllowNotSeparatelyRecorded();\r\n emit RiskVerdict(payloadHash, action, score, reasonMask, evidenceHash);\r\n }\r\n\r\n /// @notice Clear a packet the worker withheld for manual review.\r\n /// @dev Emits only; no storage. The worker observes `PacketApproved` and releases the\r\n /// packet from its local deferred queue. Approval is a human override of a risk\r\n /// verdict, so it is separated from the operator key by design — a compromised or\r\n /// buggy worker cannot approve the packets it chose to hold.\r\n function approvePacket(bytes32 payloadHash) external onlyOwner {\r\n emit PacketApproved(payloadHash, msg.sender);\r\n }\r\n\r\n function setOperator(address _operator) external onlyOwner {\r\n require(_operator != address(0), \"zero operator\");\r\n operator = _operator;\r\n emit OperatorSet(_operator);\r\n }\r\n\r\n function setReceiveUln(address _receiveUln) external onlyOwner {\r\n require(_receiveUln != address(0), \"zero receiveUln\");\r\n receiveUln = _receiveUln;\r\n emit ReceiveUlnSet(_receiveUln);\r\n }\r\n\r\n function setFee(uint256 _fee) external onlyOwner {\r\n fee = _fee;\r\n emit FeeSet(_fee);\r\n }\r\n\r\n function withdraw(address payable _to) external onlyOwner {\r\n (bool ok, ) = _to.call{ value: address(this).balance }(\"\");\r\n require(ok, \"withdraw failed\");\r\n }\r\n}\r\n" + }, + "contracts/mocks/ReceiveUlnMock.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.22;\n\n/// @notice Minimal stand-in for ReceiveUln302: records what `verify` was called with so tests\n/// can assert the DVN forwarded the attestation faithfully.\ncontract ReceiveUlnMock {\n bytes public lastHeader;\n bytes32 public lastPayloadHash;\n uint64 public lastConfirmations;\n uint256 public calls;\n\n function verify(bytes calldata _header, bytes32 _payloadHash, uint64 _confirmations) external {\n lastHeader = _header;\n lastPayloadHash = _payloadHash;\n lastConfirmations = _confirmations;\n calls++;\n }\n}\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "evmVersion": "paris", + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + "devdoc", + "userdoc", + "storageLayout", + "evm.gasEstimates" + ], + "": [ + "ast" + ] + } + }, + "metadata": { + "useLiteralContent": true + } + } +} \ No newline at end of file diff --git a/deployments/optimism-sepolia/solcInputs/8088d7b064191499b181ffda0ed40a97.json b/deployments/optimism-sepolia/solcInputs/8088d7b064191499b181ffda0ed40a97.json new file mode 100644 index 0000000..b08c7d6 --- /dev/null +++ b/deployments/optimism-sepolia/solcInputs/8088d7b064191499b181ffda0ed40a97.json @@ -0,0 +1,156 @@ +{ + "language": "Solidity", + "sources": { + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\nimport { IMessageLibManager } from \"./IMessageLibManager.sol\";\nimport { IMessagingComposer } from \"./IMessagingComposer.sol\";\nimport { IMessagingChannel } from \"./IMessagingChannel.sol\";\nimport { IMessagingContext } from \"./IMessagingContext.sol\";\n\nstruct MessagingParams {\n uint32 dstEid;\n bytes32 receiver;\n bytes message;\n bytes options;\n bool payInLzToken;\n}\n\nstruct MessagingReceipt {\n bytes32 guid;\n uint64 nonce;\n MessagingFee fee;\n}\n\nstruct MessagingFee {\n uint256 nativeFee;\n uint256 lzTokenFee;\n}\n\nstruct Origin {\n uint32 srcEid;\n bytes32 sender;\n uint64 nonce;\n}\n\ninterface ILayerZeroEndpointV2 is IMessageLibManager, IMessagingComposer, IMessagingChannel, IMessagingContext {\n event PacketSent(bytes encodedPayload, bytes options, address sendLibrary);\n\n event PacketVerified(Origin origin, address receiver, bytes32 payloadHash);\n\n event PacketDelivered(Origin origin, address receiver);\n\n event LzReceiveAlert(\n address indexed receiver,\n address indexed executor,\n Origin origin,\n bytes32 guid,\n uint256 gas,\n uint256 value,\n bytes message,\n bytes extraData,\n bytes reason\n );\n\n event LzTokenSet(address token);\n\n event DelegateSet(address sender, address delegate);\n\n function quote(MessagingParams calldata _params, address _sender) external view returns (MessagingFee memory);\n\n function send(\n MessagingParams calldata _params,\n address _refundAddress\n ) external payable returns (MessagingReceipt memory);\n\n function verify(Origin calldata _origin, address _receiver, bytes32 _payloadHash) external;\n\n function verifiable(Origin calldata _origin, address _receiver) external view returns (bool);\n\n function initializable(Origin calldata _origin, address _receiver) external view returns (bool);\n\n function lzReceive(\n Origin calldata _origin,\n address _receiver,\n bytes32 _guid,\n bytes calldata _message,\n bytes calldata _extraData\n ) external payable;\n\n // oapp can burn messages partially by calling this function with its own business logic if messages are verified in order\n function clear(address _oapp, Origin calldata _origin, bytes32 _guid, bytes calldata _message) external;\n\n function setLzToken(address _lzToken) external;\n\n function lzToken() external view returns (address);\n\n function nativeToken() external view returns (address);\n\n function setDelegate(address _delegate) external;\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroReceiver.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\nimport { Origin } from \"./ILayerZeroEndpointV2.sol\";\n\ninterface ILayerZeroReceiver {\n function allowInitializePath(Origin calldata _origin) external view returns (bool);\n\n function nextNonce(uint32 _eid, bytes32 _sender) external view returns (uint64);\n\n function lzReceive(\n Origin calldata _origin,\n bytes32 _guid,\n bytes calldata _message,\n address _executor,\n bytes calldata _extraData\n ) external payable;\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLib.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\nimport { IERC165 } from \"@openzeppelin/contracts/utils/introspection/IERC165.sol\";\n\nimport { SetConfigParam } from \"./IMessageLibManager.sol\";\n\nenum MessageLibType {\n Send,\n Receive,\n SendAndReceive\n}\n\ninterface IMessageLib is IERC165 {\n function setConfig(address _oapp, SetConfigParam[] calldata _config) external;\n\n function getConfig(uint32 _eid, address _oapp, uint32 _configType) external view returns (bytes memory config);\n\n function isSupportedEid(uint32 _eid) external view returns (bool);\n\n // message libs of same major version are compatible\n function version() external view returns (uint64 major, uint8 minor, uint8 endpointVersion);\n\n function messageLibType() external view returns (MessageLibType);\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLibManager.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\nstruct SetConfigParam {\n uint32 eid;\n uint32 configType;\n bytes config;\n}\n\ninterface IMessageLibManager {\n struct Timeout {\n address lib;\n uint256 expiry;\n }\n\n event LibraryRegistered(address newLib);\n event DefaultSendLibrarySet(uint32 eid, address newLib);\n event DefaultReceiveLibrarySet(uint32 eid, address newLib);\n event DefaultReceiveLibraryTimeoutSet(uint32 eid, address oldLib, uint256 expiry);\n event SendLibrarySet(address sender, uint32 eid, address newLib);\n event ReceiveLibrarySet(address receiver, uint32 eid, address newLib);\n event ReceiveLibraryTimeoutSet(address receiver, uint32 eid, address oldLib, uint256 timeout);\n\n function registerLibrary(address _lib) external;\n\n function isRegisteredLibrary(address _lib) external view returns (bool);\n\n function getRegisteredLibraries() external view returns (address[] memory);\n\n function setDefaultSendLibrary(uint32 _eid, address _newLib) external;\n\n function defaultSendLibrary(uint32 _eid) external view returns (address);\n\n function setDefaultReceiveLibrary(uint32 _eid, address _newLib, uint256 _gracePeriod) external;\n\n function defaultReceiveLibrary(uint32 _eid) external view returns (address);\n\n function setDefaultReceiveLibraryTimeout(uint32 _eid, address _lib, uint256 _expiry) external;\n\n function defaultReceiveLibraryTimeout(uint32 _eid) external view returns (address lib, uint256 expiry);\n\n function isSupportedEid(uint32 _eid) external view returns (bool);\n\n function isValidReceiveLibrary(address _receiver, uint32 _eid, address _lib) external view returns (bool);\n\n /// ------------------- OApp interfaces -------------------\n function setSendLibrary(address _oapp, uint32 _eid, address _newLib) external;\n\n function getSendLibrary(address _sender, uint32 _eid) external view returns (address lib);\n\n function isDefaultSendLibrary(address _sender, uint32 _eid) external view returns (bool);\n\n function setReceiveLibrary(address _oapp, uint32 _eid, address _newLib, uint256 _gracePeriod) external;\n\n function getReceiveLibrary(address _receiver, uint32 _eid) external view returns (address lib, bool isDefault);\n\n function setReceiveLibraryTimeout(address _oapp, uint32 _eid, address _lib, uint256 _expiry) external;\n\n function receiveLibraryTimeout(address _receiver, uint32 _eid) external view returns (address lib, uint256 expiry);\n\n function setConfig(address _oapp, address _lib, SetConfigParam[] calldata _params) external;\n\n function getConfig(\n address _oapp,\n address _lib,\n uint32 _eid,\n uint32 _configType\n ) external view returns (bytes memory config);\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingChannel.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\ninterface IMessagingChannel {\n event InboundNonceSkipped(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce);\n event PacketNilified(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce, bytes32 payloadHash);\n event PacketBurnt(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce, bytes32 payloadHash);\n\n function eid() external view returns (uint32);\n\n // this is an emergency function if a message cannot be verified for some reasons\n // required to provide _nextNonce to avoid race condition\n function skip(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce) external;\n\n function nilify(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes32 _payloadHash) external;\n\n function burn(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes32 _payloadHash) external;\n\n function nextGuid(address _sender, uint32 _dstEid, bytes32 _receiver) external view returns (bytes32);\n\n function inboundNonce(address _receiver, uint32 _srcEid, bytes32 _sender) external view returns (uint64);\n\n function outboundNonce(address _sender, uint32 _dstEid, bytes32 _receiver) external view returns (uint64);\n\n function inboundPayloadHash(\n address _receiver,\n uint32 _srcEid,\n bytes32 _sender,\n uint64 _nonce\n ) external view returns (bytes32);\n\n function lazyInboundNonce(address _receiver, uint32 _srcEid, bytes32 _sender) external view returns (uint64);\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingComposer.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\ninterface IMessagingComposer {\n event ComposeSent(address from, address to, bytes32 guid, uint16 index, bytes message);\n event ComposeDelivered(address from, address to, bytes32 guid, uint16 index);\n event LzComposeAlert(\n address indexed from,\n address indexed to,\n address indexed executor,\n bytes32 guid,\n uint16 index,\n uint256 gas,\n uint256 value,\n bytes message,\n bytes extraData,\n bytes reason\n );\n\n function composeQueue(\n address _from,\n address _to,\n bytes32 _guid,\n uint16 _index\n ) external view returns (bytes32 messageHash);\n\n function sendCompose(address _to, bytes32 _guid, uint16 _index, bytes calldata _message) external;\n\n function lzCompose(\n address _from,\n address _to,\n bytes32 _guid,\n uint16 _index,\n bytes calldata _message,\n bytes calldata _extraData\n ) external payable;\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingContext.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\ninterface IMessagingContext {\n function isSendingMessage() external view returns (bool);\n\n function getSendContext() external view returns (uint32 dstEid, address sender);\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ISendLib.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\nimport { MessagingFee } from \"./ILayerZeroEndpointV2.sol\";\nimport { IMessageLib } from \"./IMessageLib.sol\";\n\nstruct Packet {\n uint64 nonce;\n uint32 srcEid;\n address sender;\n uint32 dstEid;\n bytes32 receiver;\n bytes32 guid;\n bytes message;\n}\n\ninterface ISendLib is IMessageLib {\n function send(\n Packet calldata _packet,\n bytes calldata _options,\n bool _payInLzToken\n ) external returns (MessagingFee memory, bytes memory encodedPacket);\n\n function quote(\n Packet calldata _packet,\n bytes calldata _options,\n bool _payInLzToken\n ) external view returns (MessagingFee memory);\n\n function setTreasury(address _treasury) external;\n\n function withdrawFee(address _to, uint256 _amount) external;\n\n function withdrawLzTokenFee(address _lzToken, address _to, uint256 _amount) external;\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/AddressCast.sol": { + "content": "// SPDX-License-Identifier: LZBL-1.2\n\npragma solidity ^0.8.20;\n\nlibrary AddressCast {\n error AddressCast_InvalidSizeForAddress();\n error AddressCast_InvalidAddress();\n\n function toBytes32(bytes calldata _addressBytes) internal pure returns (bytes32 result) {\n if (_addressBytes.length > 32) revert AddressCast_InvalidAddress();\n result = bytes32(_addressBytes);\n unchecked {\n uint256 offset = 32 - _addressBytes.length;\n result = result >> (offset * 8);\n }\n }\n\n function toBytes32(address _address) internal pure returns (bytes32 result) {\n result = bytes32(uint256(uint160(_address)));\n }\n\n function toBytes(bytes32 _addressBytes32, uint256 _size) internal pure returns (bytes memory result) {\n if (_size == 0 || _size > 32) revert AddressCast_InvalidSizeForAddress();\n result = new bytes(_size);\n unchecked {\n uint256 offset = 256 - _size * 8;\n assembly {\n mstore(add(result, 32), shl(offset, _addressBytes32))\n }\n }\n }\n\n function toAddress(bytes32 _addressBytes32) internal pure returns (address result) {\n result = address(uint160(uint256(_addressBytes32)));\n }\n\n function toAddress(bytes calldata _addressBytes) internal pure returns (address result) {\n if (_addressBytes.length != 20) revert AddressCast_InvalidAddress();\n result = address(bytes20(_addressBytes));\n }\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.sol": { + "content": "// SPDX-License-Identifier: LZBL-1.2\n\npragma solidity ^0.8.20;\n\nimport { Packet } from \"../../interfaces/ISendLib.sol\";\nimport { AddressCast } from \"../../libs/AddressCast.sol\";\n\nlibrary PacketV1Codec {\n using AddressCast for address;\n using AddressCast for bytes32;\n\n uint8 internal constant PACKET_VERSION = 1;\n\n // header (version + nonce + path)\n // version\n uint256 private constant PACKET_VERSION_OFFSET = 0;\n // nonce\n uint256 private constant NONCE_OFFSET = 1;\n // path\n uint256 private constant SRC_EID_OFFSET = 9;\n uint256 private constant SENDER_OFFSET = 13;\n uint256 private constant DST_EID_OFFSET = 45;\n uint256 private constant RECEIVER_OFFSET = 49;\n // payload (guid + message)\n uint256 private constant GUID_OFFSET = 81; // keccak256(nonce + path)\n uint256 private constant MESSAGE_OFFSET = 113;\n\n function encode(Packet memory _packet) internal pure returns (bytes memory encodedPacket) {\n encodedPacket = abi.encodePacked(\n PACKET_VERSION,\n _packet.nonce,\n _packet.srcEid,\n _packet.sender.toBytes32(),\n _packet.dstEid,\n _packet.receiver,\n _packet.guid,\n _packet.message\n );\n }\n\n function encodePacketHeader(Packet memory _packet) internal pure returns (bytes memory) {\n return\n abi.encodePacked(\n PACKET_VERSION,\n _packet.nonce,\n _packet.srcEid,\n _packet.sender.toBytes32(),\n _packet.dstEid,\n _packet.receiver\n );\n }\n\n function encodePayload(Packet memory _packet) internal pure returns (bytes memory) {\n return abi.encodePacked(_packet.guid, _packet.message);\n }\n\n function header(bytes calldata _packet) internal pure returns (bytes calldata) {\n return _packet[0:GUID_OFFSET];\n }\n\n function version(bytes calldata _packet) internal pure returns (uint8) {\n return uint8(bytes1(_packet[PACKET_VERSION_OFFSET:NONCE_OFFSET]));\n }\n\n function nonce(bytes calldata _packet) internal pure returns (uint64) {\n return uint64(bytes8(_packet[NONCE_OFFSET:SRC_EID_OFFSET]));\n }\n\n function srcEid(bytes calldata _packet) internal pure returns (uint32) {\n return uint32(bytes4(_packet[SRC_EID_OFFSET:SENDER_OFFSET]));\n }\n\n function sender(bytes calldata _packet) internal pure returns (bytes32) {\n return bytes32(_packet[SENDER_OFFSET:DST_EID_OFFSET]);\n }\n\n function senderAddressB20(bytes calldata _packet) internal pure returns (address) {\n return sender(_packet).toAddress();\n }\n\n function dstEid(bytes calldata _packet) internal pure returns (uint32) {\n return uint32(bytes4(_packet[DST_EID_OFFSET:RECEIVER_OFFSET]));\n }\n\n function receiver(bytes calldata _packet) internal pure returns (bytes32) {\n return bytes32(_packet[RECEIVER_OFFSET:GUID_OFFSET]);\n }\n\n function receiverB20(bytes calldata _packet) internal pure returns (address) {\n return receiver(_packet).toAddress();\n }\n\n function guid(bytes calldata _packet) internal pure returns (bytes32) {\n return bytes32(_packet[GUID_OFFSET:MESSAGE_OFFSET]);\n }\n\n function message(bytes calldata _packet) internal pure returns (bytes calldata) {\n return bytes(_packet[MESSAGE_OFFSET:]);\n }\n\n function payload(bytes calldata _packet) internal pure returns (bytes calldata) {\n return bytes(_packet[GUID_OFFSET:]);\n }\n\n function payloadHash(bytes calldata _packet) internal pure returns (bytes32) {\n return keccak256(payload(_packet));\n }\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppCore.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { ILayerZeroEndpointV2 } from \"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\";\n\n/**\n * @title IOAppCore\n */\ninterface IOAppCore {\n // Custom error messages\n error OnlyPeer(uint32 eid, bytes32 sender);\n error NoPeer(uint32 eid);\n error InvalidEndpointCall();\n error InvalidDelegate();\n\n // Event emitted when a peer (OApp) is set for a corresponding endpoint\n event PeerSet(uint32 eid, bytes32 peer);\n\n /**\n * @notice Retrieves the OApp version information.\n * @return senderVersion The version of the OAppSender.sol contract.\n * @return receiverVersion The version of the OAppReceiver.sol contract.\n */\n function oAppVersion() external view returns (uint64 senderVersion, uint64 receiverVersion);\n\n /**\n * @notice Retrieves the LayerZero endpoint associated with the OApp.\n * @return iEndpoint The LayerZero endpoint as an interface.\n */\n function endpoint() external view returns (ILayerZeroEndpointV2 iEndpoint);\n\n /**\n * @notice Retrieves the peer (OApp) associated with a corresponding endpoint.\n * @param _eid The endpoint ID.\n * @return peer The peer address (OApp instance) associated with the corresponding endpoint.\n */\n function peers(uint32 _eid) external view returns (bytes32 peer);\n\n /**\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\n * @param _eid The endpoint ID.\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\n */\n function setPeer(uint32 _eid, bytes32 _peer) external;\n\n /**\n * @notice Sets the delegate address for the OApp Core.\n * @param _delegate The address of the delegate to be set.\n */\n function setDelegate(address _delegate) external;\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppMsgInspector.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\n/**\n * @title IOAppMsgInspector\n * @dev Interface for the OApp Message Inspector, allowing examination of message and options contents.\n */\ninterface IOAppMsgInspector {\n // Custom error message for inspection failure\n error InspectionFailed(bytes message, bytes options);\n\n /**\n * @notice Allows the inspector to examine LayerZero message contents and optionally throw a revert if invalid.\n * @param _message The message payload to be inspected.\n * @param _options Additional options or parameters for inspection.\n * @return valid A boolean indicating whether the inspection passed (true) or failed (false).\n *\n * @dev Optionally done as a revert, OR use the boolean provided to handle the failure.\n */\n function inspect(bytes calldata _message, bytes calldata _options) external view returns (bool valid);\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppOptionsType3.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Struct representing enforced option parameters.\n */\nstruct EnforcedOptionParam {\n uint32 eid; // Endpoint ID\n uint16 msgType; // Message Type\n bytes options; // Additional options\n}\n\n/**\n * @title IOAppOptionsType3\n * @dev Interface for the OApp with Type 3 Options, allowing the setting and combining of enforced options.\n */\ninterface IOAppOptionsType3 {\n // Custom error message for invalid options\n error InvalidOptions(bytes options);\n\n // Event emitted when enforced options are set\n event EnforcedOptionSet(EnforcedOptionParam[] _enforcedOptions);\n\n /**\n * @notice Sets enforced options for specific endpoint and message type combinations.\n * @param _enforcedOptions An array of EnforcedOptionParam structures specifying enforced options.\n */\n function setEnforcedOptions(EnforcedOptionParam[] calldata _enforcedOptions) external;\n\n /**\n * @notice Combines options for a given endpoint and message type.\n * @param _eid The endpoint ID.\n * @param _msgType The OApp message type.\n * @param _extraOptions Additional options passed by the caller.\n * @return options The combination of caller specified options AND enforced options.\n */\n function combineOptions(\n uint32 _eid,\n uint16 _msgType,\n bytes calldata _extraOptions\n ) external view returns (bytes memory options);\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppReceiver.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.20;\n\nimport { ILayerZeroReceiver, Origin } from \"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroReceiver.sol\";\n\ninterface IOAppReceiver is ILayerZeroReceiver {\n /**\n * @notice Indicates whether an address is an approved composeMsg sender to the Endpoint.\n * @param _origin The origin information containing the source endpoint and sender address.\n * - srcEid: The source chain endpoint ID.\n * - sender: The sender address on the src chain.\n * - nonce: The nonce of the message.\n * @param _message The lzReceive payload.\n * @param _sender The sender address.\n * @return isSender Is a valid sender.\n *\n * @dev Applications can optionally choose to implement a separate composeMsg sender that is NOT the bridging layer.\n * @dev The default sender IS the OAppReceiver implementer.\n */\n function isComposeMsgSender(\n Origin calldata _origin,\n bytes calldata _message,\n address _sender\n ) external view returns (bool isSender);\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { Ownable } from \"@openzeppelin/contracts/access/Ownable.sol\";\nimport { IOAppOptionsType3, EnforcedOptionParam } from \"../interfaces/IOAppOptionsType3.sol\";\n\n/**\n * @title OAppOptionsType3\n * @dev Abstract contract implementing the IOAppOptionsType3 interface with type 3 options.\n */\nabstract contract OAppOptionsType3 is IOAppOptionsType3, Ownable {\n uint16 internal constant OPTION_TYPE_3 = 3;\n\n // @dev The \"msgType\" should be defined in the child contract.\n mapping(uint32 eid => mapping(uint16 msgType => bytes enforcedOption)) public enforcedOptions;\n\n /**\n * @dev Sets the enforced options for specific endpoint and message type combinations.\n * @param _enforcedOptions An array of EnforcedOptionParam structures specifying enforced options.\n *\n * @dev Only the owner/admin of the OApp can call this function.\n * @dev Provides a way for the OApp to enforce things like paying for PreCrime, AND/OR minimum dst lzReceive gas amounts etc.\n * @dev These enforced options can vary as the potential options/execution on the remote may differ as per the msgType.\n * eg. Amount of lzReceive() gas necessary to deliver a lzCompose() message adds overhead you dont want to pay\n * if you are only making a standard LayerZero message ie. lzReceive() WITHOUT sendCompose().\n */\n function setEnforcedOptions(EnforcedOptionParam[] calldata _enforcedOptions) public virtual onlyOwner {\n _setEnforcedOptions(_enforcedOptions);\n }\n\n /**\n * @dev Sets the enforced options for specific endpoint and message type combinations.\n * @param _enforcedOptions An array of EnforcedOptionParam structures specifying enforced options.\n *\n * @dev Provides a way for the OApp to enforce things like paying for PreCrime, AND/OR minimum dst lzReceive gas amounts etc.\n * @dev These enforced options can vary as the potential options/execution on the remote may differ as per the msgType.\n * eg. Amount of lzReceive() gas necessary to deliver a lzCompose() message adds overhead you dont want to pay\n * if you are only making a standard LayerZero message ie. lzReceive() WITHOUT sendCompose().\n */\n function _setEnforcedOptions(EnforcedOptionParam[] memory _enforcedOptions) internal virtual {\n for (uint256 i = 0; i < _enforcedOptions.length; i++) {\n // @dev Enforced options are only available for optionType 3, as type 1 and 2 dont support combining.\n _assertOptionsType3(_enforcedOptions[i].options);\n enforcedOptions[_enforcedOptions[i].eid][_enforcedOptions[i].msgType] = _enforcedOptions[i].options;\n }\n\n emit EnforcedOptionSet(_enforcedOptions);\n }\n\n /**\n * @notice Combines options for a given endpoint and message type.\n * @param _eid The endpoint ID.\n * @param _msgType The OAPP message type.\n * @param _extraOptions Additional options passed by the caller.\n * @return options The combination of caller specified options AND enforced options.\n *\n * @dev If there is an enforced lzReceive option:\n * - {gasLimit: 200k, msg.value: 1 ether} AND a caller supplies a lzReceive option: {gasLimit: 100k, msg.value: 0.5 ether}\n * - The resulting options will be {gasLimit: 300k, msg.value: 1.5 ether} when the message is executed on the remote lzReceive() function.\n * @dev This presence of duplicated options is handled off-chain in the verifier/executor.\n */\n function combineOptions(\n uint32 _eid,\n uint16 _msgType,\n bytes calldata _extraOptions\n ) public view virtual returns (bytes memory) {\n bytes memory enforced = enforcedOptions[_eid][_msgType];\n\n // No enforced options, pass whatever the caller supplied, even if it's empty or legacy type 1/2 options.\n if (enforced.length == 0) return _extraOptions;\n\n // No caller options, return enforced\n if (_extraOptions.length == 0) return enforced;\n\n // @dev If caller provided _extraOptions, must be type 3 as its the ONLY type that can be combined.\n if (_extraOptions.length >= 2) {\n _assertOptionsType3(_extraOptions);\n // @dev Remove the first 2 bytes containing the type from the _extraOptions and combine with enforced.\n return bytes.concat(enforced, _extraOptions[2:]);\n }\n\n // No valid set of options was found.\n revert InvalidOptions(_extraOptions);\n }\n\n /**\n * @dev Internal function to assert that options are of type 3.\n * @param _options The options to be checked.\n */\n function _assertOptionsType3(bytes memory _options) internal pure virtual {\n uint16 optionsType;\n assembly {\n optionsType := mload(add(_options, 2))\n }\n if (optionsType != OPTION_TYPE_3) revert InvalidOptions(_options);\n }\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\n// @dev Import the 'MessagingFee' and 'MessagingReceipt' so it's exposed to OApp implementers\n// solhint-disable-next-line no-unused-import\nimport { OAppSender, MessagingFee, MessagingReceipt } from \"./OAppSender.sol\";\n// @dev Import the 'Origin' so it's exposed to OApp implementers\n// solhint-disable-next-line no-unused-import\nimport { OAppReceiver, Origin } from \"./OAppReceiver.sol\";\nimport { OAppCore } from \"./OAppCore.sol\";\n\n/**\n * @title OApp\n * @dev Abstract contract serving as the base for OApp implementation, combining OAppSender and OAppReceiver functionality.\n */\nabstract contract OApp is OAppSender, OAppReceiver {\n /**\n * @dev Constructor to initialize the OApp with the provided endpoint and owner.\n * @param _endpoint The address of the LOCAL LayerZero endpoint.\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\n */\n constructor(address _endpoint, address _delegate) OAppCore(_endpoint, _delegate) {}\n\n /**\n * @notice Retrieves the OApp version information.\n * @return senderVersion The version of the OAppSender.sol implementation.\n * @return receiverVersion The version of the OAppReceiver.sol implementation.\n */\n function oAppVersion()\n public\n pure\n virtual\n override(OAppSender, OAppReceiver)\n returns (uint64 senderVersion, uint64 receiverVersion)\n {\n return (SENDER_VERSION, RECEIVER_VERSION);\n }\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/OAppCore.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { Ownable } from \"@openzeppelin/contracts/access/Ownable.sol\";\nimport { IOAppCore, ILayerZeroEndpointV2 } from \"./interfaces/IOAppCore.sol\";\n\n/**\n * @title OAppCore\n * @dev Abstract contract implementing the IOAppCore interface with basic OApp configurations.\n */\nabstract contract OAppCore is IOAppCore, Ownable {\n // The LayerZero endpoint associated with the given OApp\n ILayerZeroEndpointV2 public immutable endpoint;\n\n // Mapping to store peers associated with corresponding endpoints\n mapping(uint32 eid => bytes32 peer) public peers;\n\n /**\n * @dev Constructor to initialize the OAppCore with the provided endpoint and delegate.\n * @param _endpoint The address of the LOCAL Layer Zero endpoint.\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\n *\n * @dev The delegate typically should be set as the owner of the contract.\n */\n constructor(address _endpoint, address _delegate) {\n endpoint = ILayerZeroEndpointV2(_endpoint);\n\n if (_delegate == address(0)) revert InvalidDelegate();\n endpoint.setDelegate(_delegate);\n }\n\n /**\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\n * @param _eid The endpoint ID.\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\n *\n * @dev Only the owner/admin of the OApp can call this function.\n * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp.\n * @dev Set this to bytes32(0) to remove the peer address.\n * @dev Peer is a bytes32 to accommodate non-evm chains.\n */\n function setPeer(uint32 _eid, bytes32 _peer) public virtual onlyOwner {\n _setPeer(_eid, _peer);\n }\n\n /**\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\n * @param _eid The endpoint ID.\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\n *\n * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp.\n * @dev Set this to bytes32(0) to remove the peer address.\n * @dev Peer is a bytes32 to accommodate non-evm chains.\n */\n function _setPeer(uint32 _eid, bytes32 _peer) internal virtual {\n peers[_eid] = _peer;\n emit PeerSet(_eid, _peer);\n }\n\n /**\n * @notice Internal function to get the peer address associated with a specific endpoint; reverts if NOT set.\n * ie. the peer is set to bytes32(0).\n * @param _eid The endpoint ID.\n * @return peer The address of the peer associated with the specified endpoint.\n */\n function _getPeerOrRevert(uint32 _eid) internal view virtual returns (bytes32) {\n bytes32 peer = peers[_eid];\n if (peer == bytes32(0)) revert NoPeer(_eid);\n return peer;\n }\n\n /**\n * @notice Sets the delegate address for the OApp.\n * @param _delegate The address of the delegate to be set.\n *\n * @dev Only the owner/admin of the OApp can call this function.\n * @dev Provides the ability for a delegate to set configs, on behalf of the OApp, directly on the Endpoint contract.\n */\n function setDelegate(address _delegate) public onlyOwner {\n endpoint.setDelegate(_delegate);\n }\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/OAppReceiver.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { IOAppReceiver, Origin } from \"./interfaces/IOAppReceiver.sol\";\nimport { OAppCore } from \"./OAppCore.sol\";\n\n/**\n * @title OAppReceiver\n * @dev Abstract contract implementing the ILayerZeroReceiver interface and extending OAppCore for OApp receivers.\n */\nabstract contract OAppReceiver is IOAppReceiver, OAppCore {\n // Custom error message for when the caller is not the registered endpoint/\n error OnlyEndpoint(address addr);\n\n // @dev The version of the OAppReceiver implementation.\n // @dev Version is bumped when changes are made to this contract.\n uint64 internal constant RECEIVER_VERSION = 2;\n\n /**\n * @notice Retrieves the OApp version information.\n * @return senderVersion The version of the OAppSender.sol contract.\n * @return receiverVersion The version of the OAppReceiver.sol contract.\n *\n * @dev Providing 0 as the default for OAppSender version. Indicates that the OAppSender is not implemented.\n * ie. this is a RECEIVE only OApp.\n * @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions.\n */\n function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) {\n return (0, RECEIVER_VERSION);\n }\n\n /**\n * @notice Indicates whether an address is an approved composeMsg sender to the Endpoint.\n * @dev _origin The origin information containing the source endpoint and sender address.\n * - srcEid: The source chain endpoint ID.\n * - sender: The sender address on the src chain.\n * - nonce: The nonce of the message.\n * @dev _message The lzReceive payload.\n * @param _sender The sender address.\n * @return isSender Is a valid sender.\n *\n * @dev Applications can optionally choose to implement separate composeMsg senders that are NOT the bridging layer.\n * @dev The default sender IS the OAppReceiver implementer.\n */\n function isComposeMsgSender(\n Origin calldata /*_origin*/,\n bytes calldata /*_message*/,\n address _sender\n ) public view virtual returns (bool) {\n return _sender == address(this);\n }\n\n /**\n * @notice Checks if the path initialization is allowed based on the provided origin.\n * @param origin The origin information containing the source endpoint and sender address.\n * @return Whether the path has been initialized.\n *\n * @dev This indicates to the endpoint that the OApp has enabled msgs for this particular path to be received.\n * @dev This defaults to assuming if a peer has been set, its initialized.\n * Can be overridden by the OApp if there is other logic to determine this.\n */\n function allowInitializePath(Origin calldata origin) public view virtual returns (bool) {\n return peers[origin.srcEid] == origin.sender;\n }\n\n /**\n * @notice Retrieves the next nonce for a given source endpoint and sender address.\n * @dev _srcEid The source endpoint ID.\n * @dev _sender The sender address.\n * @return nonce The next nonce.\n *\n * @dev The path nonce starts from 1. If 0 is returned it means that there is NO nonce ordered enforcement.\n * @dev Is required by the off-chain executor to determine the OApp expects msg execution is ordered.\n * @dev This is also enforced by the OApp.\n * @dev By default this is NOT enabled. ie. nextNonce is hardcoded to return 0.\n */\n function nextNonce(uint32 /*_srcEid*/, bytes32 /*_sender*/) public view virtual returns (uint64 nonce) {\n return 0;\n }\n\n /**\n * @dev Entry point for receiving messages or packets from the endpoint.\n * @param _origin The origin information containing the source endpoint and sender address.\n * - srcEid: The source chain endpoint ID.\n * - sender: The sender address on the src chain.\n * - nonce: The nonce of the message.\n * @param _guid The unique identifier for the received LayerZero message.\n * @param _message The payload of the received message.\n * @param _executor The address of the executor for the received message.\n * @param _extraData Additional arbitrary data provided by the corresponding executor.\n *\n * @dev Entry point for receiving msg/packet from the LayerZero endpoint.\n */\n function lzReceive(\n Origin calldata _origin,\n bytes32 _guid,\n bytes calldata _message,\n address _executor,\n bytes calldata _extraData\n ) public payable virtual {\n // Ensures that only the endpoint can attempt to lzReceive() messages to this OApp.\n if (address(endpoint) != msg.sender) revert OnlyEndpoint(msg.sender);\n\n // Ensure that the sender matches the expected peer for the source endpoint.\n if (_getPeerOrRevert(_origin.srcEid) != _origin.sender) revert OnlyPeer(_origin.srcEid, _origin.sender);\n\n // Call the internal OApp implementation of lzReceive.\n _lzReceive(_origin, _guid, _message, _executor, _extraData);\n }\n\n /**\n * @dev Internal function to implement lzReceive logic without needing to copy the basic parameter validation.\n */\n function _lzReceive(\n Origin calldata _origin,\n bytes32 _guid,\n bytes calldata _message,\n address _executor,\n bytes calldata _extraData\n ) internal virtual;\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/OAppSender.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { SafeERC20, IERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { MessagingParams, MessagingFee, MessagingReceipt } from \"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\";\nimport { OAppCore } from \"./OAppCore.sol\";\n\n/**\n * @title OAppSender\n * @dev Abstract contract implementing the OAppSender functionality for sending messages to a LayerZero endpoint.\n */\nabstract contract OAppSender is OAppCore {\n using SafeERC20 for IERC20;\n\n // Custom error messages\n error NotEnoughNative(uint256 msgValue);\n error LzTokenUnavailable();\n\n // @dev The version of the OAppSender implementation.\n // @dev Version is bumped when changes are made to this contract.\n uint64 internal constant SENDER_VERSION = 1;\n\n /**\n * @notice Retrieves the OApp version information.\n * @return senderVersion The version of the OAppSender.sol contract.\n * @return receiverVersion The version of the OAppReceiver.sol contract.\n *\n * @dev Providing 0 as the default for OAppReceiver version. Indicates that the OAppReceiver is not implemented.\n * ie. this is a SEND only OApp.\n * @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions\n */\n function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) {\n return (SENDER_VERSION, 0);\n }\n\n /**\n * @dev Internal function to interact with the LayerZero EndpointV2.quote() for fee calculation.\n * @param _dstEid The destination endpoint ID.\n * @param _message The message payload.\n * @param _options Additional options for the message.\n * @param _payInLzToken Flag indicating whether to pay the fee in LZ tokens.\n * @return fee The calculated MessagingFee for the message.\n * - nativeFee: The native fee for the message.\n * - lzTokenFee: The LZ token fee for the message.\n */\n function _quote(\n uint32 _dstEid,\n bytes memory _message,\n bytes memory _options,\n bool _payInLzToken\n ) internal view virtual returns (MessagingFee memory fee) {\n return\n endpoint.quote(\n MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _payInLzToken),\n address(this)\n );\n }\n\n /**\n * @dev Internal function to interact with the LayerZero EndpointV2.send() for sending a message.\n * @param _dstEid The destination endpoint ID.\n * @param _message The message payload.\n * @param _options Additional options for the message.\n * @param _fee The calculated LayerZero fee for the message.\n * - nativeFee: The native fee.\n * - lzTokenFee: The lzToken fee.\n * @param _refundAddress The address to receive any excess fee values sent to the endpoint.\n * @return receipt The receipt for the sent message.\n * - guid: The unique identifier for the sent message.\n * - nonce: The nonce of the sent message.\n * - fee: The LayerZero fee incurred for the message.\n */\n function _lzSend(\n uint32 _dstEid,\n bytes memory _message,\n bytes memory _options,\n MessagingFee memory _fee,\n address _refundAddress\n ) internal virtual returns (MessagingReceipt memory receipt) {\n // @dev Push corresponding fees to the endpoint, any excess is sent back to the _refundAddress from the endpoint.\n uint256 messageValue = _payNative(_fee.nativeFee);\n if (_fee.lzTokenFee > 0) _payLzToken(_fee.lzTokenFee);\n\n return\n // solhint-disable-next-line check-send-result\n endpoint.send{ value: messageValue }(\n MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _fee.lzTokenFee > 0),\n _refundAddress\n );\n }\n\n /**\n * @dev Internal function to pay the native fee associated with the message.\n * @param _nativeFee The native fee to be paid.\n * @return nativeFee The amount of native currency paid.\n *\n * @dev If the OApp needs to initiate MULTIPLE LayerZero messages in a single transaction,\n * this will need to be overridden because msg.value would contain multiple lzFees.\n * @dev Should be overridden in the event the LayerZero endpoint requires a different native currency.\n * @dev Some EVMs use an ERC20 as a method for paying transactions/gasFees.\n * @dev The endpoint is EITHER/OR, ie. it will NOT support both types of native payment at a time.\n */\n function _payNative(uint256 _nativeFee) internal virtual returns (uint256 nativeFee) {\n if (msg.value != _nativeFee) revert NotEnoughNative(msg.value);\n return _nativeFee;\n }\n\n /**\n * @dev Internal function to pay the LZ token fee associated with the message.\n * @param _lzTokenFee The LZ token fee to be paid.\n *\n * @dev If the caller is trying to pay in the specified lzToken, then the lzTokenFee is passed to the endpoint.\n * @dev Any excess sent, is passed back to the specified _refundAddress in the _lzSend().\n */\n function _payLzToken(uint256 _lzTokenFee) internal virtual {\n // @dev Cannot cache the token because it is not immutable in the endpoint.\n address lzToken = endpoint.lzToken();\n if (lzToken == address(0)) revert LzTokenUnavailable();\n\n // Pay LZ token fee by sending tokens to the endpoint.\n IERC20(lzToken).safeTransferFrom(msg.sender, address(endpoint), _lzTokenFee);\n }\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/precrime/interfaces/IOAppPreCrimeSimulator.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\n// @dev Import the Origin so it's exposed to OAppPreCrimeSimulator implementers.\n// solhint-disable-next-line no-unused-import\nimport { InboundPacket, Origin } from \"../libs/Packet.sol\";\n\n/**\n * @title IOAppPreCrimeSimulator Interface\n * @dev Interface for the preCrime simulation functionality in an OApp.\n */\ninterface IOAppPreCrimeSimulator {\n // @dev simulation result used in PreCrime implementation\n error SimulationResult(bytes result);\n error OnlySelf();\n\n /**\n * @dev Emitted when the preCrime contract address is set.\n * @param preCrimeAddress The address of the preCrime contract.\n */\n event PreCrimeSet(address preCrimeAddress);\n\n /**\n * @dev Retrieves the address of the preCrime contract implementation.\n * @return The address of the preCrime contract.\n */\n function preCrime() external view returns (address);\n\n /**\n * @dev Retrieves the address of the OApp contract.\n * @return The address of the OApp contract.\n */\n function oApp() external view returns (address);\n\n /**\n * @dev Sets the preCrime contract address.\n * @param _preCrime The address of the preCrime contract.\n */\n function setPreCrime(address _preCrime) external;\n\n /**\n * @dev Mocks receiving a packet, then reverts with a series of data to infer the state/result.\n * @param _packets An array of LayerZero InboundPacket objects representing received packets.\n */\n function lzReceiveAndRevert(InboundPacket[] calldata _packets) external payable;\n\n /**\n * @dev checks if the specified peer is considered 'trusted' by the OApp.\n * @param _eid The endpoint Id to check.\n * @param _peer The peer to check.\n * @return Whether the peer passed is considered 'trusted' by the OApp.\n */\n function isPeer(uint32 _eid, bytes32 _peer) external view returns (bool);\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/precrime/interfaces/IPreCrime.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\nstruct PreCrimePeer {\n uint32 eid;\n bytes32 preCrime;\n bytes32 oApp;\n}\n\n// TODO not done yet\ninterface IPreCrime {\n error OnlyOffChain();\n\n // for simulate()\n error PacketOversize(uint256 max, uint256 actual);\n error PacketUnsorted();\n error SimulationFailed(bytes reason);\n\n // for preCrime()\n error SimulationResultNotFound(uint32 eid);\n error InvalidSimulationResult(uint32 eid, bytes reason);\n error CrimeFound(bytes crime);\n\n function getConfig(bytes[] calldata _packets, uint256[] calldata _packetMsgValues) external returns (bytes memory);\n\n function simulate(\n bytes[] calldata _packets,\n uint256[] calldata _packetMsgValues\n ) external payable returns (bytes memory);\n\n function buildSimulationResult() external view returns (bytes memory);\n\n function preCrime(\n bytes[] calldata _packets,\n uint256[] calldata _packetMsgValues,\n bytes[] calldata _simulations\n ) external;\n\n function version() external view returns (uint64 major, uint8 minor);\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/precrime/libs/Packet.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { Origin } from \"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\";\nimport { PacketV1Codec } from \"@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.sol\";\n\n/**\n * @title InboundPacket\n * @dev Structure representing an inbound packet received by the contract.\n */\nstruct InboundPacket {\n Origin origin; // Origin information of the packet.\n uint32 dstEid; // Destination endpointId of the packet.\n address receiver; // Receiver address for the packet.\n bytes32 guid; // Unique identifier of the packet.\n uint256 value; // msg.value of the packet.\n address executor; // Executor address for the packet.\n bytes message; // Message payload of the packet.\n bytes extraData; // Additional arbitrary data for the packet.\n}\n\n/**\n * @title PacketDecoder\n * @dev Library for decoding LayerZero packets.\n */\nlibrary PacketDecoder {\n using PacketV1Codec for bytes;\n\n /**\n * @dev Decode an inbound packet from the given packet data.\n * @param _packet The packet data to decode.\n * @return packet An InboundPacket struct representing the decoded packet.\n */\n function decode(bytes calldata _packet) internal pure returns (InboundPacket memory packet) {\n packet.origin = Origin(_packet.srcEid(), _packet.sender(), _packet.nonce());\n packet.dstEid = _packet.dstEid();\n packet.receiver = _packet.receiverB20();\n packet.guid = _packet.guid();\n packet.message = _packet.message();\n }\n\n /**\n * @dev Decode multiple inbound packets from the given packet data and associated message values.\n * @param _packets An array of packet data to decode.\n * @param _packetMsgValues An array of associated message values for each packet.\n * @return packets An array of InboundPacket structs representing the decoded packets.\n */\n function decode(\n bytes[] calldata _packets,\n uint256[] memory _packetMsgValues\n ) internal pure returns (InboundPacket[] memory packets) {\n packets = new InboundPacket[](_packets.length);\n for (uint256 i = 0; i < _packets.length; i++) {\n bytes calldata packet = _packets[i];\n packets[i] = PacketDecoder.decode(packet);\n // @dev Allows the verifier to specify the msg.value that gets passed in lzReceive.\n packets[i].value = _packetMsgValues[i];\n }\n }\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/precrime/OAppPreCrimeSimulator.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { Ownable } from \"@openzeppelin/contracts/access/Ownable.sol\";\nimport { IPreCrime } from \"./interfaces/IPreCrime.sol\";\nimport { IOAppPreCrimeSimulator, InboundPacket, Origin } from \"./interfaces/IOAppPreCrimeSimulator.sol\";\n\n/**\n * @title OAppPreCrimeSimulator\n * @dev Abstract contract serving as the base for preCrime simulation functionality in an OApp.\n */\nabstract contract OAppPreCrimeSimulator is IOAppPreCrimeSimulator, Ownable {\n // The address of the preCrime implementation.\n address public preCrime;\n\n /**\n * @dev Retrieves the address of the OApp contract.\n * @return The address of the OApp contract.\n *\n * @dev The simulator contract is the base contract for the OApp by default.\n * @dev If the simulator is a separate contract, override this function.\n */\n function oApp() external view virtual returns (address) {\n return address(this);\n }\n\n /**\n * @dev Sets the preCrime contract address.\n * @param _preCrime The address of the preCrime contract.\n */\n function setPreCrime(address _preCrime) public virtual onlyOwner {\n preCrime = _preCrime;\n emit PreCrimeSet(_preCrime);\n }\n\n /**\n * @dev Interface for pre-crime simulations. Always reverts at the end with the simulation results.\n * @param _packets An array of InboundPacket objects representing received packets to be delivered.\n *\n * @dev WARNING: MUST revert at the end with the simulation results.\n * @dev Gives the preCrime implementation the ability to mock sending packets to the lzReceive function,\n * WITHOUT actually executing them.\n */\n function lzReceiveAndRevert(InboundPacket[] calldata _packets) public payable virtual {\n for (uint256 i = 0; i < _packets.length; i++) {\n InboundPacket calldata packet = _packets[i];\n\n // Ignore packets that are not from trusted peers.\n if (!isPeer(packet.origin.srcEid, packet.origin.sender)) continue;\n\n // @dev Because a verifier is calling this function, it doesnt have access to executor params:\n // - address _executor\n // - bytes calldata _extraData\n // preCrime will NOT work for OApps that rely on these two parameters inside of their _lzReceive().\n // They are instead stubbed to default values, address(0) and bytes(\"\")\n // @dev Calling this.lzReceiveSimulate removes ability for assembly return 0 callstack exit,\n // which would cause the revert to be ignored.\n this.lzReceiveSimulate{ value: packet.value }(\n packet.origin,\n packet.guid,\n packet.message,\n packet.executor,\n packet.extraData\n );\n }\n\n // @dev Revert with the simulation results. msg.sender must implement IPreCrime.buildSimulationResult().\n revert SimulationResult(IPreCrime(msg.sender).buildSimulationResult());\n }\n\n /**\n * @dev Is effectively an internal function because msg.sender must be address(this).\n * Allows resetting the call stack for 'internal' calls.\n * @param _origin The origin information containing the source endpoint and sender address.\n * - srcEid: The source chain endpoint ID.\n * - sender: The sender address on the src chain.\n * - nonce: The nonce of the message.\n * @param _guid The unique identifier of the packet.\n * @param _message The message payload of the packet.\n * @param _executor The executor address for the packet.\n * @param _extraData Additional data for the packet.\n */\n function lzReceiveSimulate(\n Origin calldata _origin,\n bytes32 _guid,\n bytes calldata _message,\n address _executor,\n bytes calldata _extraData\n ) external payable virtual {\n // @dev Ensure ONLY can be called 'internally'.\n if (msg.sender != address(this)) revert OnlySelf();\n _lzReceiveSimulate(_origin, _guid, _message, _executor, _extraData);\n }\n\n /**\n * @dev Internal function to handle the OAppPreCrimeSimulator simulated receive.\n * @param _origin The origin information.\n * - srcEid: The source chain endpoint ID.\n * - sender: The sender address from the src chain.\n * - nonce: The nonce of the LayerZero message.\n * @param _guid The GUID of the LayerZero message.\n * @param _message The LayerZero message.\n * @param _executor The address of the off-chain executor.\n * @param _extraData Arbitrary data passed by the msg executor.\n *\n * @dev Enables the preCrime simulator to mock sending lzReceive() messages,\n * routes the msg down from the OAppPreCrimeSimulator, and back up to the OAppReceiver.\n */\n function _lzReceiveSimulate(\n Origin calldata _origin,\n bytes32 _guid,\n bytes calldata _message,\n address _executor,\n bytes calldata _extraData\n ) internal virtual;\n\n /**\n * @dev checks if the specified peer is considered 'trusted' by the OApp.\n * @param _eid The endpoint Id to check.\n * @param _peer The peer to check.\n * @return Whether the peer passed is considered 'trusted' by the OApp.\n */\n function isPeer(uint32 _eid, bytes32 _peer) public view virtual returns (bool);\n}\n" + }, + "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { MessagingReceipt, MessagingFee } from \"@layerzerolabs/oapp-evm/contracts/oapp/OAppSender.sol\";\n\n/**\n * @dev Struct representing token parameters for the OFT send() operation.\n */\nstruct SendParam {\n uint32 dstEid; // Destination endpoint ID.\n bytes32 to; // Recipient address.\n uint256 amountLD; // Amount to send in local decimals.\n uint256 minAmountLD; // Minimum amount to send in local decimals.\n bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message.\n bytes composeMsg; // The composed message for the send() operation.\n bytes oftCmd; // The OFT command to be executed, unused in default OFT implementations.\n}\n\n/**\n * @dev Struct representing OFT limit information.\n * @dev These amounts can change dynamically and are up the specific oft implementation.\n */\nstruct OFTLimit {\n uint256 minAmountLD; // Minimum amount in local decimals that can be sent to the recipient.\n uint256 maxAmountLD; // Maximum amount in local decimals that can be sent to the recipient.\n}\n\n/**\n * @dev Struct representing OFT receipt information.\n */\nstruct OFTReceipt {\n uint256 amountSentLD; // Amount of tokens ACTUALLY debited from the sender in local decimals.\n // @dev In non-default implementations, the amountReceivedLD COULD differ from this value.\n uint256 amountReceivedLD; // Amount of tokens to be received on the remote side.\n}\n\n/**\n * @dev Struct representing OFT fee details.\n * @dev Future proof mechanism to provide a standardized way to communicate fees to things like a UI.\n */\nstruct OFTFeeDetail {\n int256 feeAmountLD; // Amount of the fee in local decimals.\n string description; // Description of the fee.\n}\n\n/**\n * @title IOFT\n * @dev Interface for the OftChain (OFT) token.\n * @dev Does not inherit ERC20 to accommodate usage by OFTAdapter as well.\n * @dev This specific interface ID is '0x02e49c2c'.\n */\ninterface IOFT {\n // Custom error messages\n error InvalidLocalDecimals();\n error SlippageExceeded(uint256 amountLD, uint256 minAmountLD);\n error AmountSDOverflowed(uint256 amountSD);\n\n // Events\n event OFTSent(\n bytes32 indexed guid, // GUID of the OFT message.\n uint32 dstEid, // Destination Endpoint ID.\n address indexed fromAddress, // Address of the sender on the src chain.\n uint256 amountSentLD, // Amount of tokens sent in local decimals.\n uint256 amountReceivedLD // Amount of tokens received in local decimals.\n );\n event OFTReceived(\n bytes32 indexed guid, // GUID of the OFT message.\n uint32 srcEid, // Source Endpoint ID.\n address indexed toAddress, // Address of the recipient on the dst chain.\n uint256 amountReceivedLD // Amount of tokens received in local decimals.\n );\n\n /**\n * @notice Retrieves interfaceID and the version of the OFT.\n * @return interfaceId The interface ID.\n * @return version The version.\n *\n * @dev interfaceId: This specific interface ID is '0x02e49c2c'.\n * @dev version: Indicates a cross-chain compatible msg encoding with other OFTs.\n * @dev If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented.\n * ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)\n */\n function oftVersion() external view returns (bytes4 interfaceId, uint64 version);\n\n /**\n * @notice Retrieves the address of the token associated with the OFT.\n * @return token The address of the ERC20 token implementation.\n */\n function token() external view returns (address);\n\n /**\n * @notice Indicates whether the OFT contract requires approval of the 'token()' to send.\n * @return requiresApproval Needs approval of the underlying token implementation.\n *\n * @dev Allows things like wallet implementers to determine integration requirements,\n * without understanding the underlying token implementation.\n */\n function approvalRequired() external view returns (bool);\n\n /**\n * @notice Retrieves the shared decimals of the OFT.\n * @return sharedDecimals The shared decimals of the OFT.\n */\n function sharedDecimals() external view returns (uint8);\n\n /**\n * @notice Provides the fee breakdown and settings data for an OFT. Unused in the default implementation.\n * @param _sendParam The parameters for the send operation.\n * @return limit The OFT limit information.\n * @return oftFeeDetails The details of OFT fees.\n * @return receipt The OFT receipt information.\n */\n function quoteOFT(\n SendParam calldata _sendParam\n ) external view returns (OFTLimit memory, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory);\n\n /**\n * @notice Provides a quote for the send() operation.\n * @param _sendParam The parameters for the send() operation.\n * @param _payInLzToken Flag indicating whether the caller is paying in the LZ token.\n * @return fee The calculated LayerZero messaging fee from the send() operation.\n *\n * @dev MessagingFee: LayerZero msg fee\n * - nativeFee: The native fee.\n * - lzTokenFee: The lzToken fee.\n */\n function quoteSend(SendParam calldata _sendParam, bool _payInLzToken) external view returns (MessagingFee memory);\n\n /**\n * @notice Executes the send() operation.\n * @param _sendParam The parameters for the send operation.\n * @param _fee The fee information supplied by the caller.\n * - nativeFee: The native fee.\n * - lzTokenFee: The lzToken fee.\n * @param _refundAddress The address to receive any excess funds from fees etc. on the src.\n * @return receipt The LayerZero messaging receipt from the send() operation.\n * @return oftReceipt The OFT receipt information.\n *\n * @dev MessagingReceipt: LayerZero msg receipt\n * - guid: The unique identifier for the sent message.\n * - nonce: The nonce of the sent message.\n * - fee: The LayerZero fee incurred for the message.\n */\n function send(\n SendParam calldata _sendParam,\n MessagingFee calldata _fee,\n address _refundAddress\n ) external payable returns (MessagingReceipt memory, OFTReceipt memory);\n}\n" + }, + "@layerzerolabs/oft-evm/contracts/libs/OFTComposeMsgCodec.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nlibrary OFTComposeMsgCodec {\n // Offset constants for decoding composed messages\n uint8 private constant NONCE_OFFSET = 8;\n uint8 private constant SRC_EID_OFFSET = 12;\n uint8 private constant AMOUNT_LD_OFFSET = 44;\n uint8 private constant COMPOSE_FROM_OFFSET = 76;\n\n /**\n * @dev Encodes a OFT composed message.\n * @param _nonce The nonce value.\n * @param _srcEid The source endpoint ID.\n * @param _amountLD The amount in local decimals.\n * @param _composeMsg The composed message.\n * @return _msg The encoded Composed message.\n */\n function encode(\n uint64 _nonce,\n uint32 _srcEid,\n uint256 _amountLD,\n bytes memory _composeMsg // 0x[composeFrom][composeMsg]\n ) internal pure returns (bytes memory _msg) {\n _msg = abi.encodePacked(_nonce, _srcEid, _amountLD, _composeMsg);\n }\n\n /**\n * @dev Retrieves the nonce for the composed message.\n * @param _msg The message.\n * @return The nonce value.\n */\n function nonce(bytes calldata _msg) internal pure returns (uint64) {\n return uint64(bytes8(_msg[:NONCE_OFFSET]));\n }\n\n /**\n * @dev Retrieves the source endpoint ID for the composed message.\n * @param _msg The message.\n * @return The source endpoint ID.\n */\n function srcEid(bytes calldata _msg) internal pure returns (uint32) {\n return uint32(bytes4(_msg[NONCE_OFFSET:SRC_EID_OFFSET]));\n }\n\n /**\n * @dev Retrieves the amount in local decimals from the composed message.\n * @param _msg The message.\n * @return The amount in local decimals.\n */\n function amountLD(bytes calldata _msg) internal pure returns (uint256) {\n return uint256(bytes32(_msg[SRC_EID_OFFSET:AMOUNT_LD_OFFSET]));\n }\n\n /**\n * @dev Retrieves the composeFrom value from the composed message.\n * @param _msg The message.\n * @return The composeFrom value.\n */\n function composeFrom(bytes calldata _msg) internal pure returns (bytes32) {\n return bytes32(_msg[AMOUNT_LD_OFFSET:COMPOSE_FROM_OFFSET]);\n }\n\n /**\n * @dev Retrieves the composed message.\n * @param _msg The message.\n * @return The composed message.\n */\n function composeMsg(bytes calldata _msg) internal pure returns (bytes memory) {\n return _msg[COMPOSE_FROM_OFFSET:];\n }\n\n /**\n * @dev Converts an address to bytes32.\n * @param _addr The address to convert.\n * @return The bytes32 representation of the address.\n */\n function addressToBytes32(address _addr) internal pure returns (bytes32) {\n return bytes32(uint256(uint160(_addr)));\n }\n\n /**\n * @dev Converts bytes32 to an address.\n * @param _b The bytes32 value to convert.\n * @return The address representation of bytes32.\n */\n function bytes32ToAddress(bytes32 _b) internal pure returns (address) {\n return address(uint160(uint256(_b)));\n }\n}\n" + }, + "@layerzerolabs/oft-evm/contracts/libs/OFTMsgCodec.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nlibrary OFTMsgCodec {\n // Offset constants for encoding and decoding OFT messages\n uint8 private constant SEND_TO_OFFSET = 32;\n uint8 private constant SEND_AMOUNT_SD_OFFSET = 40;\n\n /**\n * @dev Encodes an OFT LayerZero message.\n * @param _sendTo The recipient address.\n * @param _amountShared The amount in shared decimals.\n * @param _composeMsg The composed message.\n * @return _msg The encoded message.\n * @return hasCompose A boolean indicating whether the message has a composed payload.\n */\n function encode(\n bytes32 _sendTo,\n uint64 _amountShared,\n bytes memory _composeMsg\n ) internal view returns (bytes memory _msg, bool hasCompose) {\n hasCompose = _composeMsg.length > 0;\n // @dev Remote chains will want to know the composed function caller ie. msg.sender on the src.\n _msg = hasCompose\n ? abi.encodePacked(_sendTo, _amountShared, addressToBytes32(msg.sender), _composeMsg)\n : abi.encodePacked(_sendTo, _amountShared);\n }\n\n /**\n * @dev Checks if the OFT message is composed.\n * @param _msg The OFT message.\n * @return A boolean indicating whether the message is composed.\n */\n function isComposed(bytes calldata _msg) internal pure returns (bool) {\n return _msg.length > SEND_AMOUNT_SD_OFFSET;\n }\n\n /**\n * @dev Retrieves the recipient address from the OFT message.\n * @param _msg The OFT message.\n * @return The recipient address.\n */\n function sendTo(bytes calldata _msg) internal pure returns (bytes32) {\n return bytes32(_msg[:SEND_TO_OFFSET]);\n }\n\n /**\n * @dev Retrieves the amount in shared decimals from the OFT message.\n * @param _msg The OFT message.\n * @return The amount in shared decimals.\n */\n function amountSD(bytes calldata _msg) internal pure returns (uint64) {\n return uint64(bytes8(_msg[SEND_TO_OFFSET:SEND_AMOUNT_SD_OFFSET]));\n }\n\n /**\n * @dev Retrieves the composed message from the OFT message.\n * @param _msg The OFT message.\n * @return The composed message.\n */\n function composeMsg(bytes calldata _msg) internal pure returns (bytes memory) {\n return _msg[SEND_AMOUNT_SD_OFFSET:];\n }\n\n /**\n * @dev Converts an address to bytes32.\n * @param _addr The address to convert.\n * @return The bytes32 representation of the address.\n */\n function addressToBytes32(address _addr) internal pure returns (bytes32) {\n return bytes32(uint256(uint160(_addr)));\n }\n\n /**\n * @dev Converts bytes32 to an address.\n * @param _b The bytes32 value to convert.\n * @return The address representation of bytes32.\n */\n function bytes32ToAddress(bytes32 _b) internal pure returns (address) {\n return address(uint160(uint256(_b)));\n }\n}\n" + }, + "@layerzerolabs/oft-evm/contracts/OFT.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport { IOFT, OFTCore } from \"./OFTCore.sol\";\n\n/**\n * @title OFT Contract\n * @dev OFT is an ERC-20 token that extends the functionality of the OFTCore contract.\n */\nabstract contract OFT is OFTCore, ERC20 {\n /**\n * @dev Constructor for the OFT contract.\n * @param _name The name of the OFT.\n * @param _symbol The symbol of the OFT.\n * @param _lzEndpoint The LayerZero endpoint address.\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\n */\n constructor(\n string memory _name,\n string memory _symbol,\n address _lzEndpoint,\n address _delegate\n ) ERC20(_name, _symbol) OFTCore(decimals(), _lzEndpoint, _delegate) {}\n\n /**\n * @dev Retrieves the address of the underlying ERC20 implementation.\n * @return The address of the OFT token.\n *\n * @dev In the case of OFT, address(this) and erc20 are the same contract.\n */\n function token() public view returns (address) {\n return address(this);\n }\n\n /**\n * @notice Indicates whether the OFT contract requires approval of the 'token()' to send.\n * @return requiresApproval Needs approval of the underlying token implementation.\n *\n * @dev In the case of OFT where the contract IS the token, approval is NOT required.\n */\n function approvalRequired() external pure virtual returns (bool) {\n return false;\n }\n\n /**\n * @dev Burns tokens from the sender's specified balance.\n * @param _from The address to debit the tokens from.\n * @param _amountLD The amount of tokens to send in local decimals.\n * @param _minAmountLD The minimum amount to send in local decimals.\n * @param _dstEid The destination chain ID.\n * @return amountSentLD The amount sent in local decimals.\n * @return amountReceivedLD The amount received in local decimals on the remote.\n */\n function _debit(\n address _from,\n uint256 _amountLD,\n uint256 _minAmountLD,\n uint32 _dstEid\n ) internal virtual override returns (uint256 amountSentLD, uint256 amountReceivedLD) {\n (amountSentLD, amountReceivedLD) = _debitView(_amountLD, _minAmountLD, _dstEid);\n\n // @dev In NON-default OFT, amountSentLD could be 100, with a 10% fee, the amountReceivedLD amount is 90,\n // therefore amountSentLD CAN differ from amountReceivedLD.\n\n // @dev Default OFT burns on src.\n _burn(_from, amountSentLD);\n }\n\n /**\n * @dev Credits tokens to the specified address.\n * @param _to The address to credit the tokens to.\n * @param _amountLD The amount of tokens to credit in local decimals.\n * @dev _srcEid The source chain ID.\n * @return amountReceivedLD The amount of tokens ACTUALLY received in local decimals.\n */\n function _credit(\n address _to,\n uint256 _amountLD,\n uint32 /*_srcEid*/\n ) internal virtual override returns (uint256 amountReceivedLD) {\n if (_to == address(0x0)) _to = address(0xdead); // _mint(...) does not support address(0x0)\n // @dev Default OFT mints on dst.\n _mint(_to, _amountLD);\n // @dev In the case of NON-default OFT, the _amountLD MIGHT not be == amountReceivedLD.\n return _amountLD;\n }\n}\n" + }, + "@layerzerolabs/oft-evm/contracts/OFTCore.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\nimport { OApp, Origin } from \"@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol\";\nimport { OAppOptionsType3 } from \"@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol\";\nimport { IOAppMsgInspector } from \"@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppMsgInspector.sol\";\n\nimport { OAppPreCrimeSimulator } from \"@layerzerolabs/oapp-evm/contracts/precrime/OAppPreCrimeSimulator.sol\";\n\nimport { IOFT, SendParam, OFTLimit, OFTReceipt, OFTFeeDetail, MessagingReceipt, MessagingFee } from \"./interfaces/IOFT.sol\";\nimport { OFTMsgCodec } from \"./libs/OFTMsgCodec.sol\";\nimport { OFTComposeMsgCodec } from \"./libs/OFTComposeMsgCodec.sol\";\n\n/**\n * @title OFTCore\n * @dev Abstract contract for the OftChain (OFT) token.\n */\nabstract contract OFTCore is IOFT, OApp, OAppPreCrimeSimulator, OAppOptionsType3 {\n using OFTMsgCodec for bytes;\n using OFTMsgCodec for bytes32;\n\n // @notice Provides a conversion rate when swapping between denominations of SD and LD\n // - shareDecimals == SD == shared Decimals\n // - localDecimals == LD == local decimals\n // @dev Considers that tokens have different decimal amounts on various chains.\n // @dev eg.\n // For a token\n // - locally with 4 decimals --> 1.2345 => uint(12345)\n // - remotely with 2 decimals --> 1.23 => uint(123)\n // - The conversion rate would be 10 ** (4 - 2) = 100\n // @dev If you want to send 1.2345 -> (uint 12345), you CANNOT represent that value on the remote,\n // you can only display 1.23 -> uint(123).\n // @dev To preserve the dust that would otherwise be lost on that conversion,\n // we need to unify a denomination that can be represented on ALL chains inside of the OFT mesh\n uint256 public immutable decimalConversionRate;\n\n // @notice Msg types that are used to identify the various OFT operations.\n // @dev This can be extended in child contracts for non-default oft operations\n // @dev These values are used in things like combineOptions() in OAppOptionsType3.sol.\n uint16 public constant SEND = 1;\n uint16 public constant SEND_AND_CALL = 2;\n\n // Address of an optional contract to inspect both 'message' and 'options'\n address public msgInspector;\n event MsgInspectorSet(address inspector);\n\n /**\n * @dev Constructor.\n * @param _localDecimals The decimals of the token on the local chain (this chain).\n * @param _endpoint The address of the LayerZero endpoint.\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\n */\n constructor(uint8 _localDecimals, address _endpoint, address _delegate) OApp(_endpoint, _delegate) {\n if (_localDecimals < sharedDecimals()) revert InvalidLocalDecimals();\n decimalConversionRate = 10 ** (_localDecimals - sharedDecimals());\n }\n\n /**\n * @notice Retrieves interfaceID and the version of the OFT.\n * @return interfaceId The interface ID.\n * @return version The version.\n *\n * @dev interfaceId: This specific interface ID is '0x02e49c2c'.\n * @dev version: Indicates a cross-chain compatible msg encoding with other OFTs.\n * @dev If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented.\n * ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)\n */\n function oftVersion() external pure virtual returns (bytes4 interfaceId, uint64 version) {\n return (type(IOFT).interfaceId, 1);\n }\n\n /**\n * @dev Retrieves the shared decimals of the OFT.\n * @return The shared decimals of the OFT.\n *\n * @dev Sets an implicit cap on the amount of tokens, over uint64.max() will need some sort of outbound cap / totalSupply cap\n * Lowest common decimal denominator between chains.\n * Defaults to 6 decimal places to provide up to 18,446,744,073,709.551615 units (max uint64).\n * For tokens exceeding this totalSupply(), they will need to override the sharedDecimals function with something smaller.\n * ie. 4 sharedDecimals would be 1,844,674,407,370,955.1615\n */\n function sharedDecimals() public view virtual returns (uint8) {\n return 6;\n }\n\n /**\n * @dev Sets the message inspector address for the OFT.\n * @param _msgInspector The address of the message inspector.\n *\n * @dev This is an optional contract that can be used to inspect both 'message' and 'options'.\n * @dev Set it to address(0) to disable it, or set it to a contract address to enable it.\n */\n function setMsgInspector(address _msgInspector) public virtual onlyOwner {\n msgInspector = _msgInspector;\n emit MsgInspectorSet(_msgInspector);\n }\n\n /**\n * @notice Provides the fee breakdown and settings data for an OFT. Unused in the default implementation.\n * @param _sendParam The parameters for the send operation.\n * @return oftLimit The OFT limit information.\n * @return oftFeeDetails The details of OFT fees.\n * @return oftReceipt The OFT receipt information.\n */\n function quoteOFT(\n SendParam calldata _sendParam\n )\n external\n view\n virtual\n returns (OFTLimit memory oftLimit, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory oftReceipt)\n {\n uint256 minAmountLD = 0; // Unused in the default implementation.\n uint256 maxAmountLD = IERC20(this.token()).totalSupply(); // Unused in the default implementation.\n oftLimit = OFTLimit(minAmountLD, maxAmountLD);\n\n // Unused in the default implementation; reserved for future complex fee details.\n oftFeeDetails = new OFTFeeDetail[](0);\n\n // @dev This is the same as the send() operation, but without the actual send.\n // - amountSentLD is the amount in local decimals that would be sent from the sender.\n // - amountReceivedLD is the amount in local decimals that will be credited to the recipient on the remote OFT instance.\n // @dev The amountSentLD MIGHT not equal the amount the user actually receives. HOWEVER, the default does.\n (uint256 amountSentLD, uint256 amountReceivedLD) = _debitView(\n _sendParam.amountLD,\n _sendParam.minAmountLD,\n _sendParam.dstEid\n );\n oftReceipt = OFTReceipt(amountSentLD, amountReceivedLD);\n }\n\n /**\n * @notice Provides a quote for the send() operation.\n * @param _sendParam The parameters for the send() operation.\n * @param _payInLzToken Flag indicating whether the caller is paying in the LZ token.\n * @return msgFee The calculated LayerZero messaging fee from the send() operation.\n *\n * @dev MessagingFee: LayerZero msg fee\n * - nativeFee: The native fee.\n * - lzTokenFee: The lzToken fee.\n */\n function quoteSend(\n SendParam calldata _sendParam,\n bool _payInLzToken\n ) external view virtual returns (MessagingFee memory msgFee) {\n // @dev mock the amount to receive, this is the same operation used in the send().\n // The quote is as similar as possible to the actual send() operation.\n (, uint256 amountReceivedLD) = _debitView(_sendParam.amountLD, _sendParam.minAmountLD, _sendParam.dstEid);\n\n // @dev Builds the options and OFT message to quote in the endpoint.\n (bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam, amountReceivedLD);\n\n // @dev Calculates the LayerZero fee for the send() operation.\n return _quote(_sendParam.dstEid, message, options, _payInLzToken);\n }\n\n /**\n * @dev Executes the send operation.\n * @param _sendParam The parameters for the send operation.\n * @param _fee The calculated fee for the send() operation.\n * - nativeFee: The native fee.\n * - lzTokenFee: The lzToken fee.\n * @param _refundAddress The address to receive any excess funds.\n * @return msgReceipt The receipt for the send operation.\n * @return oftReceipt The OFT receipt information.\n *\n * @dev MessagingReceipt: LayerZero msg receipt\n * - guid: The unique identifier for the sent message.\n * - nonce: The nonce of the sent message.\n * - fee: The LayerZero fee incurred for the message.\n */\n function send(\n SendParam calldata _sendParam,\n MessagingFee calldata _fee,\n address _refundAddress\n ) external payable virtual returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) {\n return _send(_sendParam, _fee, _refundAddress);\n }\n\n /**\n * @dev Internal function to execute the send operation.\n * @param _sendParam The parameters for the send operation.\n * @param _fee The calculated fee for the send() operation.\n * - nativeFee: The native fee.\n * - lzTokenFee: The lzToken fee.\n * @param _refundAddress The address to receive any excess funds.\n * @return msgReceipt The receipt for the send operation.\n * @return oftReceipt The OFT receipt information.\n *\n * @dev MessagingReceipt: LayerZero msg receipt\n * - guid: The unique identifier for the sent message.\n * - nonce: The nonce of the sent message.\n * - fee: The LayerZero fee incurred for the message.\n */\n function _send(\n SendParam calldata _sendParam,\n MessagingFee calldata _fee,\n address _refundAddress\n ) internal virtual returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) {\n // @dev Applies the token transfers regarding this send() operation.\n // - amountSentLD is the amount in local decimals that was ACTUALLY sent/debited from the sender.\n // - amountReceivedLD is the amount in local decimals that will be received/credited to the recipient on the remote OFT instance.\n (uint256 amountSentLD, uint256 amountReceivedLD) = _debit(\n msg.sender,\n _sendParam.amountLD,\n _sendParam.minAmountLD,\n _sendParam.dstEid\n );\n\n // @dev Builds the options and OFT message to quote in the endpoint.\n (bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam, amountReceivedLD);\n\n // @dev Sends the message to the LayerZero endpoint and returns the LayerZero msg receipt.\n msgReceipt = _lzSend(_sendParam.dstEid, message, options, _fee, _refundAddress);\n // @dev Formulate the OFT receipt.\n oftReceipt = OFTReceipt(amountSentLD, amountReceivedLD);\n\n emit OFTSent(msgReceipt.guid, _sendParam.dstEid, msg.sender, amountSentLD, amountReceivedLD);\n }\n\n /**\n * @dev Internal function to build the message and options.\n * @param _sendParam The parameters for the send() operation.\n * @param _amountLD The amount in local decimals.\n * @return message The encoded message.\n * @return options The encoded options.\n */\n function _buildMsgAndOptions(\n SendParam calldata _sendParam,\n uint256 _amountLD\n ) internal view virtual returns (bytes memory message, bytes memory options) {\n bool hasCompose;\n // @dev This generated message has the msg.sender encoded into the payload so the remote knows who the caller is.\n (message, hasCompose) = OFTMsgCodec.encode(\n _sendParam.to,\n _toSD(_amountLD),\n // @dev Must be include a non empty bytes if you want to compose, EVEN if you dont need it on the remote.\n // EVEN if you dont require an arbitrary payload to be sent... eg. '0x01'\n _sendParam.composeMsg\n );\n // @dev Change the msg type depending if its composed or not.\n uint16 msgType = hasCompose ? SEND_AND_CALL : SEND;\n // @dev Combine the callers _extraOptions with the enforced options via the OAppOptionsType3.\n options = combineOptions(_sendParam.dstEid, msgType, _sendParam.extraOptions);\n\n // @dev Optionally inspect the message and options depending if the OApp owner has set a msg inspector.\n // @dev If it fails inspection, needs to revert in the implementation. ie. does not rely on return boolean\n address inspector = msgInspector; // caches the msgInspector to avoid potential double storage read\n if (inspector != address(0)) IOAppMsgInspector(inspector).inspect(message, options);\n }\n\n /**\n * @dev Internal function to handle the receive on the LayerZero endpoint.\n * @param _origin The origin information.\n * - srcEid: The source chain endpoint ID.\n * - sender: The sender address from the src chain.\n * - nonce: The nonce of the LayerZero message.\n * @param _guid The unique identifier for the received LayerZero message.\n * @param _message The encoded message.\n * @dev _executor The address of the executor.\n * @dev _extraData Additional data.\n */\n function _lzReceive(\n Origin calldata _origin,\n bytes32 _guid,\n bytes calldata _message,\n address /*_executor*/, // @dev unused in the default implementation.\n bytes calldata /*_extraData*/ // @dev unused in the default implementation.\n ) internal virtual override {\n // @dev The src sending chain doesnt know the address length on this chain (potentially non-evm)\n // Thus everything is bytes32() encoded in flight.\n address toAddress = _message.sendTo().bytes32ToAddress();\n // @dev Credit the amountLD to the recipient and return the ACTUAL amount the recipient received in local decimals\n uint256 amountReceivedLD = _credit(toAddress, _toLD(_message.amountSD()), _origin.srcEid);\n\n if (_message.isComposed()) {\n // @dev Proprietary composeMsg format for the OFT.\n bytes memory composeMsg = OFTComposeMsgCodec.encode(\n _origin.nonce,\n _origin.srcEid,\n amountReceivedLD,\n _message.composeMsg()\n );\n\n // @dev Stores the lzCompose payload that will be executed in a separate tx.\n // Standardizes functionality for executing arbitrary contract invocation on some non-evm chains.\n // @dev The off-chain executor will listen and process the msg based on the src-chain-callers compose options passed.\n // @dev The index is used when a OApp needs to compose multiple msgs on lzReceive.\n // For default OFT implementation there is only 1 compose msg per lzReceive, thus its always 0.\n endpoint.sendCompose(toAddress, _guid, 0 /* the index of the composed message*/, composeMsg);\n }\n\n emit OFTReceived(_guid, _origin.srcEid, toAddress, amountReceivedLD);\n }\n\n /**\n * @dev Internal function to handle the OAppPreCrimeSimulator simulated receive.\n * @param _origin The origin information.\n * - srcEid: The source chain endpoint ID.\n * - sender: The sender address from the src chain.\n * - nonce: The nonce of the LayerZero message.\n * @param _guid The unique identifier for the received LayerZero message.\n * @param _message The LayerZero message.\n * @param _executor The address of the off-chain executor.\n * @param _extraData Arbitrary data passed by the msg executor.\n *\n * @dev Enables the preCrime simulator to mock sending lzReceive() messages,\n * routes the msg down from the OAppPreCrimeSimulator, and back up to the OAppReceiver.\n */\n function _lzReceiveSimulate(\n Origin calldata _origin,\n bytes32 _guid,\n bytes calldata _message,\n address _executor,\n bytes calldata _extraData\n ) internal virtual override {\n _lzReceive(_origin, _guid, _message, _executor, _extraData);\n }\n\n /**\n * @dev Check if the peer is considered 'trusted' by the OApp.\n * @param _eid The endpoint ID to check.\n * @param _peer The peer to check.\n * @return Whether the peer passed is considered 'trusted' by the OApp.\n *\n * @dev Enables OAppPreCrimeSimulator to check whether a potential Inbound Packet is from a trusted source.\n */\n function isPeer(uint32 _eid, bytes32 _peer) public view virtual override returns (bool) {\n return peers[_eid] == _peer;\n }\n\n /**\n * @dev Internal function to remove dust from the given local decimal amount.\n * @param _amountLD The amount in local decimals.\n * @return amountLD The amount after removing dust.\n *\n * @dev Prevents the loss of dust when moving amounts between chains with different decimals.\n * @dev eg. uint(123) with a conversion rate of 100 becomes uint(100).\n */\n function _removeDust(uint256 _amountLD) internal view virtual returns (uint256 amountLD) {\n return (_amountLD / decimalConversionRate) * decimalConversionRate;\n }\n\n /**\n * @dev Internal function to convert an amount from shared decimals into local decimals.\n * @param _amountSD The amount in shared decimals.\n * @return amountLD The amount in local decimals.\n */\n function _toLD(uint64 _amountSD) internal view virtual returns (uint256 amountLD) {\n return _amountSD * decimalConversionRate;\n }\n\n /**\n * @dev Internal function to convert an amount from local decimals into shared decimals.\n * @param _amountLD The amount in local decimals.\n * @return amountSD The amount in shared decimals.\n *\n * @dev Reverts if the _amountLD in shared decimals overflows uint64.\n * @dev eg. uint(2**64 + 123) with a conversion rate of 1 wraps around 2**64 to uint(123).\n */\n function _toSD(uint256 _amountLD) internal view virtual returns (uint64 amountSD) {\n uint256 _amountSD = _amountLD / decimalConversionRate;\n if (_amountSD > type(uint64).max) revert AmountSDOverflowed(_amountSD);\n return uint64(_amountSD);\n }\n\n /**\n * @dev Internal function to mock the amount mutation from a OFT debit() operation.\n * @param _amountLD The amount to send in local decimals.\n * @param _minAmountLD The minimum amount to send in local decimals.\n * @dev _dstEid The destination endpoint ID.\n * @return amountSentLD The amount sent, in local decimals.\n * @return amountReceivedLD The amount to be received on the remote chain, in local decimals.\n *\n * @dev This is where things like fees would be calculated and deducted from the amount to be received on the remote.\n */\n function _debitView(\n uint256 _amountLD,\n uint256 _minAmountLD,\n uint32 /*_dstEid*/\n ) internal view virtual returns (uint256 amountSentLD, uint256 amountReceivedLD) {\n // @dev Remove the dust so nothing is lost on the conversion between chains with different decimals for the token.\n amountSentLD = _removeDust(_amountLD);\n // @dev The amount to send is the same as amount received in the default implementation.\n amountReceivedLD = amountSentLD;\n\n // @dev Check for slippage.\n if (amountReceivedLD < _minAmountLD) {\n revert SlippageExceeded(amountReceivedLD, _minAmountLD);\n }\n }\n\n /**\n * @dev Internal function to perform a debit operation.\n * @param _from The address to debit.\n * @param _amountLD The amount to send in local decimals.\n * @param _minAmountLD The minimum amount to send in local decimals.\n * @param _dstEid The destination endpoint ID.\n * @return amountSentLD The amount sent in local decimals.\n * @return amountReceivedLD The amount received in local decimals on the remote.\n *\n * @dev Defined here but are intended to be overriden depending on the OFT implementation.\n * @dev Depending on OFT implementation the _amountLD could differ from the amountReceivedLD.\n */\n function _debit(\n address _from,\n uint256 _amountLD,\n uint256 _minAmountLD,\n uint32 _dstEid\n ) internal virtual returns (uint256 amountSentLD, uint256 amountReceivedLD);\n\n /**\n * @dev Internal function to perform a credit operation.\n * @param _to The address to credit.\n * @param _amountLD The amount to credit in local decimals.\n * @param _srcEid The source endpoint ID.\n * @return amountReceivedLD The amount ACTUALLY received in local decimals.\n *\n * @dev Defined here but are intended to be overriden depending on the OFT implementation.\n * @dev Depending on OFT implementation the _amountLD could differ from the amountReceivedLD.\n */\n function _credit(\n address _to,\n uint256 _amountLD,\n uint32 _srcEid\n ) internal virtual returns (uint256 amountReceivedLD);\n}\n" + }, + "@openzeppelin/contracts/access/Ownable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\n\npragma solidity ^0.8.20;\n\nimport {Context} from \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * The initial owner is set to the address provided by the deployer. This can\n * later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n /**\n * @dev The caller account is not authorized to perform an operation.\n */\n error OwnableUnauthorizedAccount(address account);\n\n /**\n * @dev The owner is not a valid owner account. (eg. `address(0)`)\n */\n error OwnableInvalidOwner(address owner);\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\n */\n constructor(address initialOwner) {\n if (initialOwner == address(0)) {\n revert OwnableInvalidOwner(address(0));\n }\n _transferOwnership(initialOwner);\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n if (owner() != _msgSender()) {\n revert OwnableUnauthorizedAccount(_msgSender());\n }\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby disabling any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n if (newOwner == address(0)) {\n revert OwnableInvalidOwner(address(0));\n }\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n" + }, + "@openzeppelin/contracts/interfaces/draft-IERC6093.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.5.0) (interfaces/draft-IERC6093.sol)\n\npragma solidity >=0.8.4;\n\n/**\n * @dev Standard ERC-20 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-20 tokens.\n */\ninterface IERC20Errors {\n /**\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n * @param balance Current balance for the interacting account.\n * @param needed Minimum amount required to perform a transfer.\n */\n error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);\n\n /**\n * @dev Indicates a failure with the token `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n */\n error ERC20InvalidSender(address sender);\n\n /**\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\n * @param receiver Address to which tokens are being transferred.\n */\n error ERC20InvalidReceiver(address receiver);\n\n /**\n * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.\n * @param spender Address that may be allowed to operate on tokens without being their owner.\n * @param allowance Amount of tokens a `spender` is allowed to operate with.\n * @param needed Minimum amount required to perform a transfer.\n */\n error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);\n\n /**\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n * @param approver Address initiating an approval operation.\n */\n error ERC20InvalidApprover(address approver);\n\n /**\n * @dev Indicates a failure with the `spender` to be approved. Used in approvals.\n * @param spender Address that may be allowed to operate on tokens without being their owner.\n */\n error ERC20InvalidSpender(address spender);\n}\n\n/**\n * @dev Standard ERC-721 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-721 tokens.\n */\ninterface IERC721Errors {\n /**\n * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-721.\n * Used in balance queries.\n * @param owner Address of the current owner of a token.\n */\n error ERC721InvalidOwner(address owner);\n\n /**\n * @dev Indicates a `tokenId` whose `owner` is the zero address.\n * @param tokenId Identifier number of a token.\n */\n error ERC721NonexistentToken(uint256 tokenId);\n\n /**\n * @dev Indicates an error related to the ownership over a particular token. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n * @param tokenId Identifier number of a token.\n * @param owner Address of the current owner of a token.\n */\n error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);\n\n /**\n * @dev Indicates a failure with the token `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n */\n error ERC721InvalidSender(address sender);\n\n /**\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\n * @param receiver Address to which tokens are being transferred.\n */\n error ERC721InvalidReceiver(address receiver);\n\n /**\n * @dev Indicates a failure with the `operator`’s approval. Used in transfers.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n * @param tokenId Identifier number of a token.\n */\n error ERC721InsufficientApproval(address operator, uint256 tokenId);\n\n /**\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n * @param approver Address initiating an approval operation.\n */\n error ERC721InvalidApprover(address approver);\n\n /**\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n */\n error ERC721InvalidOperator(address operator);\n}\n\n/**\n * @dev Standard ERC-1155 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-1155 tokens.\n */\ninterface IERC1155Errors {\n /**\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n * @param balance Current balance for the interacting account.\n * @param needed Minimum amount required to perform a transfer.\n * @param tokenId Identifier number of a token.\n */\n error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);\n\n /**\n * @dev Indicates a failure with the token `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n */\n error ERC1155InvalidSender(address sender);\n\n /**\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\n * @param receiver Address to which tokens are being transferred.\n */\n error ERC1155InvalidReceiver(address receiver);\n\n /**\n * @dev Indicates a failure with the `operator`’s approval. Used in transfers.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n * @param owner Address of the current owner of a token.\n */\n error ERC1155MissingApprovalForAll(address operator, address owner);\n\n /**\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n * @param approver Address initiating an approval operation.\n */\n error ERC1155InvalidApprover(address approver);\n\n /**\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n */\n error ERC1155InvalidOperator(address operator);\n\n /**\n * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.\n * Used in batch transfers.\n * @param idsLength Length of the array of token identifiers\n * @param valuesLength Length of the array of token amounts\n */\n error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);\n}\n" + }, + "@openzeppelin/contracts/interfaces/IERC1363.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1363.sol)\n\npragma solidity >=0.6.2;\n\nimport {IERC20} from \"./IERC20.sol\";\nimport {IERC165} from \"./IERC165.sol\";\n\n/**\n * @title IERC1363\n * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].\n *\n * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract\n * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.\n */\ninterface IERC1363 is IERC20, IERC165 {\n /*\n * Note: the ERC-165 identifier for this interface is 0xb0202a11.\n * 0xb0202a11 ===\n * bytes4(keccak256('transferAndCall(address,uint256)')) ^\n * bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^\n * bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^\n * bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^\n * bytes4(keccak256('approveAndCall(address,uint256)')) ^\n * bytes4(keccak256('approveAndCall(address,uint256,bytes)'))\n */\n\n /**\n * @dev Moves a `value` amount of tokens from the caller's account to `to`\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n * @param to The address which you want to transfer to.\n * @param value The amount of tokens to be transferred.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function transferAndCall(address to, uint256 value) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from the caller's account to `to`\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n * @param to The address which you want to transfer to.\n * @param value The amount of tokens to be transferred.\n * @param data Additional data with no specified format, sent in call to `to`.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n * @param from The address which you want to send tokens from.\n * @param to The address which you want to transfer to.\n * @param value The amount of tokens to be transferred.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function transferFromAndCall(address from, address to, uint256 value) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n * @param from The address which you want to send tokens from.\n * @param to The address which you want to transfer to.\n * @param value The amount of tokens to be transferred.\n * @param data Additional data with no specified format, sent in call to `to`.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);\n\n /**\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.\n * @param spender The address which will spend the funds.\n * @param value The amount of tokens to be spent.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function approveAndCall(address spender, uint256 value) external returns (bool);\n\n /**\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.\n * @param spender The address which will spend the funds.\n * @param value The amount of tokens to be spent.\n * @param data Additional data with no specified format, sent in call to `spender`.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);\n}\n" + }, + "@openzeppelin/contracts/interfaces/IERC165.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC165.sol)\n\npragma solidity >=0.4.16;\n\nimport {IERC165} from \"../utils/introspection/IERC165.sol\";\n" + }, + "@openzeppelin/contracts/interfaces/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC20.sol)\n\npragma solidity >=0.4.16;\n\nimport {IERC20} from \"../token/ERC20/IERC20.sol\";\n" + }, + "@openzeppelin/contracts/token/ERC20/ERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.5.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"./IERC20.sol\";\nimport {IERC20Metadata} from \"./extensions/IERC20Metadata.sol\";\nimport {Context} from \"../../utils/Context.sol\";\nimport {IERC20Errors} from \"../../interfaces/draft-IERC6093.sol\";\n\n/**\n * @dev Implementation of the {IERC20} interface.\n *\n * This implementation is agnostic to the way tokens are created. This means\n * that a supply mechanism has to be added in a derived contract using {_mint}.\n *\n * TIP: For a detailed writeup see our guide\n * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How\n * to implement supply mechanisms].\n *\n * The default value of {decimals} is 18. To change this, you should override\n * this function so it returns a different value.\n *\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\n * instead returning `false` on failure. This behavior is nonetheless\n * conventional and does not conflict with the expectations of ERC-20\n * applications.\n */\nabstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {\n mapping(address account => uint256) private _balances;\n\n mapping(address account => mapping(address spender => uint256)) private _allowances;\n\n uint256 private _totalSupply;\n\n string private _name;\n string private _symbol;\n\n /**\n * @dev Sets the values for {name} and {symbol}.\n *\n * Both values are immutable: they can only be set once during construction.\n */\n constructor(string memory name_, string memory symbol_) {\n _name = name_;\n _symbol = symbol_;\n }\n\n /**\n * @dev Returns the name of the token.\n */\n function name() public view virtual returns (string memory) {\n return _name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view virtual returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei. This is the default value returned by this function, unless\n * it's overridden.\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view virtual returns (uint8) {\n return 18;\n }\n\n /// @inheritdoc IERC20\n function totalSupply() public view virtual returns (uint256) {\n return _totalSupply;\n }\n\n /// @inheritdoc IERC20\n function balanceOf(address account) public view virtual returns (uint256) {\n return _balances[account];\n }\n\n /**\n * @dev See {IERC20-transfer}.\n *\n * Requirements:\n *\n * - `to` cannot be the zero address.\n * - the caller must have a balance of at least `value`.\n */\n function transfer(address to, uint256 value) public virtual returns (bool) {\n address owner = _msgSender();\n _transfer(owner, to, value);\n return true;\n }\n\n /// @inheritdoc IERC20\n function allowance(address owner, address spender) public view virtual returns (uint256) {\n return _allowances[owner][spender];\n }\n\n /**\n * @dev See {IERC20-approve}.\n *\n * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on\n * `transferFrom`. This is semantically equivalent to an infinite approval.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function approve(address spender, uint256 value) public virtual returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, value);\n return true;\n }\n\n /**\n * @dev See {IERC20-transferFrom}.\n *\n * Skips emitting an {Approval} event indicating an allowance update. This is not\n * required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].\n *\n * NOTE: Does not update the allowance if the current allowance\n * is the maximum `uint256`.\n *\n * Requirements:\n *\n * - `from` and `to` cannot be the zero address.\n * - `from` must have a balance of at least `value`.\n * - the caller must have allowance for ``from``'s tokens of at least\n * `value`.\n */\n function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {\n address spender = _msgSender();\n _spendAllowance(from, spender, value);\n _transfer(from, to, value);\n return true;\n }\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to`.\n *\n * This internal function is equivalent to {transfer}, and can be used to\n * e.g. implement automatic token fees, slashing mechanisms, etc.\n *\n * Emits a {Transfer} event.\n *\n * NOTE: This function is not virtual, {_update} should be overridden instead.\n */\n function _transfer(address from, address to, uint256 value) internal {\n if (from == address(0)) {\n revert ERC20InvalidSender(address(0));\n }\n if (to == address(0)) {\n revert ERC20InvalidReceiver(address(0));\n }\n _update(from, to, value);\n }\n\n /**\n * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`\n * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding\n * this function.\n *\n * Emits a {Transfer} event.\n */\n function _update(address from, address to, uint256 value) internal virtual {\n if (from == address(0)) {\n // Overflow check required: The rest of the code assumes that totalSupply never overflows\n _totalSupply += value;\n } else {\n uint256 fromBalance = _balances[from];\n if (fromBalance < value) {\n revert ERC20InsufficientBalance(from, fromBalance, value);\n }\n unchecked {\n // Overflow not possible: value <= fromBalance <= totalSupply.\n _balances[from] = fromBalance - value;\n }\n }\n\n if (to == address(0)) {\n unchecked {\n // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.\n _totalSupply -= value;\n }\n } else {\n unchecked {\n // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.\n _balances[to] += value;\n }\n }\n\n emit Transfer(from, to, value);\n }\n\n /**\n * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).\n * Relies on the `_update` mechanism\n *\n * Emits a {Transfer} event with `from` set to the zero address.\n *\n * NOTE: This function is not virtual, {_update} should be overridden instead.\n */\n function _mint(address account, uint256 value) internal {\n if (account == address(0)) {\n revert ERC20InvalidReceiver(address(0));\n }\n _update(address(0), account, value);\n }\n\n /**\n * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.\n * Relies on the `_update` mechanism.\n *\n * Emits a {Transfer} event with `to` set to the zero address.\n *\n * NOTE: This function is not virtual, {_update} should be overridden instead\n */\n function _burn(address account, uint256 value) internal {\n if (account == address(0)) {\n revert ERC20InvalidSender(address(0));\n }\n _update(account, address(0), value);\n }\n\n /**\n * @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.\n *\n * This internal function is equivalent to `approve`, and can be used to\n * e.g. set automatic allowances for certain subsystems, etc.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `owner` cannot be the zero address.\n * - `spender` cannot be the zero address.\n *\n * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.\n */\n function _approve(address owner, address spender, uint256 value) internal {\n _approve(owner, spender, value, true);\n }\n\n /**\n * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.\n *\n * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by\n * `_spendAllowance` during the `transferFrom` operation sets the flag to false. This saves gas by not emitting any\n * `Approval` event during `transferFrom` operations.\n *\n * Anyone who wishes to continue emitting `Approval` events on the `transferFrom` operation can force the flag to\n * true using the following override:\n *\n * ```solidity\n * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {\n * super._approve(owner, spender, value, true);\n * }\n * ```\n *\n * Requirements are the same as {_approve}.\n */\n function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {\n if (owner == address(0)) {\n revert ERC20InvalidApprover(address(0));\n }\n if (spender == address(0)) {\n revert ERC20InvalidSpender(address(0));\n }\n _allowances[owner][spender] = value;\n if (emitEvent) {\n emit Approval(owner, spender, value);\n }\n }\n\n /**\n * @dev Updates `owner`'s allowance for `spender` based on spent `value`.\n *\n * Does not update the allowance value in case of infinite allowance.\n * Revert if not enough allowance is available.\n *\n * Does not emit an {Approval} event.\n */\n function _spendAllowance(address owner, address spender, uint256 value) internal virtual {\n uint256 currentAllowance = allowance(owner, spender);\n if (currentAllowance < type(uint256).max) {\n if (currentAllowance < value) {\n revert ERC20InsufficientAllowance(spender, currentAllowance, value);\n }\n unchecked {\n _approve(owner, spender, currentAllowance - value, false);\n }\n }\n }\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/extensions/IERC20Metadata.sol)\n\npragma solidity >=0.6.2;\n\nimport {IERC20} from \"../IERC20.sol\";\n\n/**\n * @dev Interface for the optional metadata functions from the ERC-20 standard.\n */\ninterface IERC20Metadata is IERC20 {\n /**\n * @dev Returns the name of the token.\n */\n function name() external view returns (string memory);\n\n /**\n * @dev Returns the symbol of the token.\n */\n function symbol() external view returns (string memory);\n\n /**\n * @dev Returns the decimals places of the token.\n */\n function decimals() external view returns (uint8);\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)\n\npragma solidity >=0.4.16;\n\n/**\n * @dev Interface of the ERC-20 standard as defined in the ERC.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the value of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the value of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves a `value` amount of tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 value) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n * caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 value) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to` using the\n * allowance mechanism. `value` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address from, address to, uint256 value) external returns (bool);\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.5.0) (token/ERC20/utils/SafeERC20.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"../IERC20.sol\";\nimport {IERC1363} from \"../../../interfaces/IERC1363.sol\";\n\n/**\n * @title SafeERC20\n * @dev Wrappers around ERC-20 operations that throw on failure (when the token\n * contract returns false). Tokens that return no value (and instead revert or\n * throw on failure) are also supported, non-reverting calls are assumed to be\n * successful.\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\n */\nlibrary SafeERC20 {\n /**\n * @dev An operation with an ERC-20 token failed.\n */\n error SafeERC20FailedOperation(address token);\n\n /**\n * @dev Indicates a failed `decreaseAllowance` request.\n */\n error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);\n\n /**\n * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,\n * non-reverting calls are assumed to be successful.\n */\n function safeTransfer(IERC20 token, address to, uint256 value) internal {\n if (!_safeTransfer(token, to, value, true)) {\n revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the\n * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.\n */\n function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {\n if (!_safeTransferFrom(token, from, to, value, true)) {\n revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.\n */\n function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {\n return _safeTransfer(token, to, value, false);\n }\n\n /**\n * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.\n */\n function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {\n return _safeTransferFrom(token, from, to, value, false);\n }\n\n /**\n * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,\n * non-reverting calls are assumed to be successful.\n *\n * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the \"client\"\n * smart contract uses ERC-7674 to set temporary allowances, then the \"client\" smart contract should avoid using\n * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract\n * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.\n */\n function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {\n uint256 oldAllowance = token.allowance(address(this), spender);\n forceApprove(token, spender, oldAllowance + value);\n }\n\n /**\n * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no\n * value, non-reverting calls are assumed to be successful.\n *\n * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the \"client\"\n * smart contract uses ERC-7674 to set temporary allowances, then the \"client\" smart contract should avoid using\n * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract\n * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.\n */\n function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {\n unchecked {\n uint256 currentAllowance = token.allowance(address(this), spender);\n if (currentAllowance < requestedDecrease) {\n revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);\n }\n forceApprove(token, spender, currentAllowance - requestedDecrease);\n }\n }\n\n /**\n * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,\n * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval\n * to be set to zero before setting it to a non-zero value, such as USDT.\n *\n * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function\n * only sets the \"standard\" allowance. Any temporary allowance will remain active, in addition to the value being\n * set here.\n */\n function forceApprove(IERC20 token, address spender, uint256 value) internal {\n if (!_safeApprove(token, spender, value, false)) {\n if (!_safeApprove(token, spender, 0, true)) revert SafeERC20FailedOperation(address(token));\n if (!_safeApprove(token, spender, value, true)) revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no\n * code. This can be used to implement an {ERC721}-like safe transfer that relies on {ERC1363} checks when\n * targeting contracts.\n *\n * Reverts if the returned value is other than `true`.\n */\n function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {\n if (to.code.length == 0) {\n safeTransfer(token, to, value);\n } else if (!token.transferAndCall(to, value, data)) {\n revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target\n * has no code. This can be used to implement an {ERC721}-like safe transfer that relies on {ERC1363} checks when\n * targeting contracts.\n *\n * Reverts if the returned value is other than `true`.\n */\n function transferFromAndCallRelaxed(\n IERC1363 token,\n address from,\n address to,\n uint256 value,\n bytes memory data\n ) internal {\n if (to.code.length == 0) {\n safeTransferFrom(token, from, to, value);\n } else if (!token.transferFromAndCall(from, to, value, data)) {\n revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no\n * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when\n * targeting contracts.\n *\n * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.\n * Oppositely, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}\n * once without retrying, and relies on the returned value to be true.\n *\n * Reverts if the returned value is other than `true`.\n */\n function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {\n if (to.code.length == 0) {\n forceApprove(token, to, value);\n } else if (!token.approveAndCall(to, value, data)) {\n revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Imitates a Solidity `token.transfer(to, value)` call, relaxing the requirement on the return value: the\n * return value is optional (but if data is returned, it must not be false).\n *\n * @param token The token targeted by the call.\n * @param to The recipient of the tokens\n * @param value The amount of token to transfer\n * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.\n */\n function _safeTransfer(IERC20 token, address to, uint256 value, bool bubble) private returns (bool success) {\n bytes4 selector = IERC20.transfer.selector;\n\n assembly (\"memory-safe\") {\n let fmp := mload(0x40)\n mstore(0x00, selector)\n mstore(0x04, and(to, shr(96, not(0))))\n mstore(0x24, value)\n success := call(gas(), token, 0, 0x00, 0x44, 0x00, 0x20)\n // if call success and return is true, all is good.\n // otherwise (not success or return is not true), we need to perform further checks\n if iszero(and(success, eq(mload(0x00), 1))) {\n // if the call was a failure and bubble is enabled, bubble the error\n if and(iszero(success), bubble) {\n returndatacopy(fmp, 0x00, returndatasize())\n revert(fmp, returndatasize())\n }\n // if the return value is not true, then the call is only successful if:\n // - the token address has code\n // - the returndata is empty\n success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))\n }\n mstore(0x40, fmp)\n }\n }\n\n /**\n * @dev Imitates a Solidity `token.transferFrom(from, to, value)` call, relaxing the requirement on the return\n * value: the return value is optional (but if data is returned, it must not be false).\n *\n * @param token The token targeted by the call.\n * @param from The sender of the tokens\n * @param to The recipient of the tokens\n * @param value The amount of token to transfer\n * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.\n */\n function _safeTransferFrom(\n IERC20 token,\n address from,\n address to,\n uint256 value,\n bool bubble\n ) private returns (bool success) {\n bytes4 selector = IERC20.transferFrom.selector;\n\n assembly (\"memory-safe\") {\n let fmp := mload(0x40)\n mstore(0x00, selector)\n mstore(0x04, and(from, shr(96, not(0))))\n mstore(0x24, and(to, shr(96, not(0))))\n mstore(0x44, value)\n success := call(gas(), token, 0, 0x00, 0x64, 0x00, 0x20)\n // if call success and return is true, all is good.\n // otherwise (not success or return is not true), we need to perform further checks\n if iszero(and(success, eq(mload(0x00), 1))) {\n // if the call was a failure and bubble is enabled, bubble the error\n if and(iszero(success), bubble) {\n returndatacopy(fmp, 0x00, returndatasize())\n revert(fmp, returndatasize())\n }\n // if the return value is not true, then the call is only successful if:\n // - the token address has code\n // - the returndata is empty\n success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))\n }\n mstore(0x40, fmp)\n mstore(0x60, 0)\n }\n }\n\n /**\n * @dev Imitates a Solidity `token.approve(spender, value)` call, relaxing the requirement on the return value:\n * the return value is optional (but if data is returned, it must not be false).\n *\n * @param token The token targeted by the call.\n * @param spender The spender of the tokens\n * @param value The amount of token to transfer\n * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.\n */\n function _safeApprove(IERC20 token, address spender, uint256 value, bool bubble) private returns (bool success) {\n bytes4 selector = IERC20.approve.selector;\n\n assembly (\"memory-safe\") {\n let fmp := mload(0x40)\n mstore(0x00, selector)\n mstore(0x04, and(spender, shr(96, not(0))))\n mstore(0x24, value)\n success := call(gas(), token, 0, 0x00, 0x44, 0x00, 0x20)\n // if call success and return is true, all is good.\n // otherwise (not success or return is not true), we need to perform further checks\n if iszero(and(success, eq(mload(0x00), 1))) {\n // if the call was a failure and bubble is enabled, bubble the error\n if and(iszero(success), bubble) {\n returndatacopy(fmp, 0x00, returndatasize())\n revert(fmp, returndatasize())\n }\n // if the return value is not true, then the call is only successful if:\n // - the token address has code\n // - the returndata is empty\n success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))\n }\n mstore(0x40, fmp)\n }\n }\n}\n" + }, + "@openzeppelin/contracts/utils/Context.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n function _contextSuffixLength() internal view virtual returns (uint256) {\n return 0;\n }\n}\n" + }, + "@openzeppelin/contracts/utils/introspection/IERC165.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)\n\npragma solidity >=0.4.16;\n\n/**\n * @dev Interface of the ERC-165 standard, as defined in the\n * https://eips.ethereum.org/EIPS/eip-165[ERC].\n *\n * Implementers can declare support of contract interfaces, which can then be\n * queried by others ({ERC165Checker}).\n *\n * For an implementation, see {ERC165}.\n */\ninterface IERC165 {\n /**\n * @dev Returns true if this contract implements the interface defined by\n * `interfaceId`. See the corresponding\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]\n * to learn more about how these ids are created.\n *\n * This function call must use less than 30 000 gas.\n */\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n" + }, + "contracts/mocks/MyOFTMock.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\r\npragma solidity ^0.8.22;\r\n\r\nimport { MyOFT } from \"../MyOFT.sol\";\r\n\r\n// @dev WARNING: This is for testing purposes only\r\ncontract MyOFTMock is MyOFT {\r\n constructor(\r\n string memory _name,\r\n string memory _symbol,\r\n address _lzEndpoint,\r\n address _delegate\r\n ) MyOFT(_name, _symbol, _lzEndpoint, _delegate) {}\r\n\r\n // Now identical to the inherited MyOFT.mint, kept as an explicit override so the mock's\r\n // intent stays visible at the point tests read it.\r\n function mint(address _to, uint256 _amount) public override {\r\n _mint(_to, _amount);\r\n }\r\n}\r\n" + }, + "contracts/MyOFT.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\r\npragma solidity ^0.8.22;\r\n\r\nimport { Ownable } from \"@openzeppelin/contracts/access/Ownable.sol\";\r\nimport { OFT } from \"@layerzerolabs/oft-evm/contracts/OFT.sol\";\r\n\r\ncontract MyOFT is OFT {\r\n constructor(\r\n string memory _name,\r\n string memory _symbol,\r\n address _lzEndpoint,\r\n address _delegate\r\n ) OFT(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) {}\r\n\r\n /// @notice Open mint, TESTNET ONLY — same as ToyOFT, so the demo tasks work against either.\r\n /// @dev Deliberately unguarded: the demo mints to itself before each send, and a testnet OFT\r\n /// with no way to obtain tokens cannot be used to exercise the DVN at all. Do NOT ship\r\n /// this to a network where the token has value.\r\n /// `virtual` because MyOFTMock declares the same function for the hardhat tests.\r\n function mint(address _to, uint256 _amount) public virtual {\r\n _mint(_to, _amount);\r\n }\r\n}\r\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "evmVersion": "paris", + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + "devdoc", + "userdoc", + "storageLayout", + "evm.gasEstimates" + ], + "": [ + "ast" + ] + } + }, + "metadata": { + "useLiteralContent": true + } + } +} \ No newline at end of file diff --git a/deployments/optimism-sepolia/solcInputs/91f67dad93f3438967ecce8dd1aaa287.json b/deployments/optimism-sepolia/solcInputs/91f67dad93f3438967ecce8dd1aaa287.json new file mode 100644 index 0000000..6554eb6 --- /dev/null +++ b/deployments/optimism-sepolia/solcInputs/91f67dad93f3438967ecce8dd1aaa287.json @@ -0,0 +1,36 @@ +{ + "language": "Solidity", + "sources": { + "contracts/mocks/RiskyProxyMock.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.22;\n\n/// @title RiskyProxyMock\n/// @notice A testnet decoy that looks like an upgradeable proxy controlled by a flagged address,\n/// for exercising the risk engine's `contract_admin_risk` check.\n/// @dev The engine reads the two EIP-1967 slots directly (see `assess/providers/contract.ts`):\n/// an implementation slot that is set means the code behind this address can change, and the\n/// admin slot names whoever can change it. It then looks that admin up in the risk store —\n/// a flagged admin is the signal, because today's clean code says nothing about tomorrow's\n/// if a sanctioned party can swap it out.\n///\n/// The slots are written straight to storage rather than by deploying a real proxy: what is\n/// being demonstrated is the engine's reading of them, and a forwarding proxy would add a\n/// delegatecall path with nothing to delegate to.\ncontract RiskyProxyMock {\n /// @dev keccak256(\"eip1967.proxy.implementation\") - 1\n bytes32 private constant SLOT_IMPLEMENTATION =\n 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\n /// @dev keccak256(\"eip1967.proxy.admin\") - 1\n bytes32 private constant SLOT_ADMIN = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;\n\n /// @param _admin The address to present as able to upgrade this contract. Point it at an\n /// address the operator has flagged (e.g. one in TEST_DENYLIST) for the check to fire.\n /// @param _implementation Any non-zero address; its only job is to make the proxy slot set.\n constructor(address _admin, address _implementation) {\n require(_admin != address(0), \"zero admin\");\n require(_implementation != address(0), \"zero implementation\");\n assembly {\n sstore(SLOT_ADMIN, _admin)\n sstore(SLOT_IMPLEMENTATION, _implementation)\n }\n }\n\n /// @notice The admin as stored in the EIP-1967 slot, for anyone reading it the easy way.\n function admin() external view returns (address a) {\n assembly {\n a := sload(SLOT_ADMIN)\n }\n }\n\n /// @notice The implementation as stored in the EIP-1967 slot.\n function implementation() external view returns (address i) {\n assembly {\n i := sload(SLOT_IMPLEMENTATION)\n }\n }\n}\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "evmVersion": "paris", + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + "devdoc", + "userdoc", + "storageLayout", + "evm.gasEstimates" + ], + "": [ + "ast" + ] + } + }, + "metadata": { + "useLiteralContent": true + } + } +} \ No newline at end of file diff --git a/deployments/optimism-sepolia/solcInputs/97ae1cbcc67ee4ffae50031ebd6ec920.json b/deployments/optimism-sepolia/solcInputs/97ae1cbcc67ee4ffae50031ebd6ec920.json new file mode 100644 index 0000000..013214b --- /dev/null +++ b/deployments/optimism-sepolia/solcInputs/97ae1cbcc67ee4ffae50031ebd6ec920.json @@ -0,0 +1,48 @@ +{ + "language": "Solidity", + "sources": { + "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/ILayerZeroDVN.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\ninterface ILayerZeroDVN {\n struct AssignJobParam {\n uint32 dstEid;\n bytes packetHeader;\n bytes32 payloadHash;\n uint64 confirmations;\n address sender;\n }\n\n // @notice query price and assign jobs at the same time\n // @param _dstEid - the destination endpoint identifier\n // @param _packetHeader - version + nonce + path\n // @param _payloadHash - hash of guid + message\n // @param _confirmations - block confirmation delay before relaying blocks\n // @param _sender - the source sending contract address\n // @param _options - options\n function assignJob(AssignJobParam calldata _param, bytes calldata _options) external payable returns (uint256 fee);\n\n // @notice query the dvn fee for relaying block information to the destination chain\n // @param _dstEid the destination endpoint identifier\n // @param _confirmations - block confirmation delay before relaying blocks\n // @param _sender - the source sending contract address\n // @param _options - options\n function getFee(\n uint32 _dstEid,\n uint64 _confirmations,\n address _sender,\n bytes calldata _options\n ) external view returns (uint256 fee);\n}\n" + }, + "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/IReceiveUlnE2.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\n/// @dev should be implemented by the ReceiveUln302 contract and future ReceiveUln contracts on EndpointV2\ninterface IReceiveUlnE2 {\n /// @notice for each dvn to verify the payload\n /// @dev this function signature 0x0223536e\n function verify(bytes calldata _packetHeader, bytes32 _payloadHash, uint64 _confirmations) external;\n\n /// @notice verify the payload at endpoint, will check if all DVNs verified\n function commitVerification(bytes calldata _packetHeader, bytes32 _payloadHash) external;\n}\n" + }, + "@openzeppelin/contracts/access/Ownable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\n\npragma solidity ^0.8.20;\n\nimport {Context} from \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * The initial owner is set to the address provided by the deployer. This can\n * later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n /**\n * @dev The caller account is not authorized to perform an operation.\n */\n error OwnableUnauthorizedAccount(address account);\n\n /**\n * @dev The owner is not a valid owner account. (eg. `address(0)`)\n */\n error OwnableInvalidOwner(address owner);\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\n */\n constructor(address initialOwner) {\n if (initialOwner == address(0)) {\n revert OwnableInvalidOwner(address(0));\n }\n _transferOwnership(initialOwner);\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n if (owner() != _msgSender()) {\n revert OwnableUnauthorizedAccount(_msgSender());\n }\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby disabling any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n if (newOwner == address(0)) {\n revert OwnableInvalidOwner(address(0));\n }\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n" + }, + "@openzeppelin/contracts/utils/Context.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n function _contextSuffixLength() internal view virtual returns (uint256) {\n return 0;\n }\n}\n" + }, + "contracts/ComplianceDVN.sol": { + "content": "// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.22;\r\n\r\nimport { Ownable } from \"@openzeppelin/contracts/access/Ownable.sol\";\r\nimport { ILayerZeroDVN } from \"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/ILayerZeroDVN.sol\";\r\nimport { IReceiveUlnE2 } from \"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/IReceiveUlnE2.sol\";\r\n\r\n/// @title ComplianceDVN\r\n/// @notice Thin LayerZero V2 DVN. All compliance judgment is off-chain; the on-chain\r\n/// contract only conforms to the worker-job interface and gates the destination\r\n/// attestation behind an operator key. Withholding `submitVerification` IS the veto.\r\ncontract ComplianceDVN is ILayerZeroDVN, Ownable {\r\n address public operator; // off-chain worker key\r\n address public sendUln; // SendUln302 on this chain — the only address allowed to assign jobs\r\n address public receiveUln; // ReceiveUln302 on this chain\r\n uint256 public fee;\r\n\r\n event JobAssigned(uint32 dstEid, bytes32 payloadHash, uint64 confirmations, address sender);\r\n event OperatorSet(address operator);\r\n event SendUlnSet(address sendUln);\r\n event ReceiveUlnSet(address receiveUln);\r\n event FeeSet(uint256 fee);\r\n\r\n /// @notice A held packet cleared for verification by the owner. Deliberately owner-only:\r\n /// the worker holds only the operator key, so it cannot approve its own holds.\r\n event PacketApproved(bytes32 indexed payloadHash, address approver);\r\n\r\n /// @notice The risk decision behind a packet's outcome.\r\n /// @param payloadHash the packet this verdict is about\r\n /// @param action ACTION_* below\r\n /// @param score 0-100 risk score the action was derived from\r\n /// @param reasonMask bitmask of reason codes; bit assignments are append-only and\r\n /// documented in the worker's `assess/verdict.ts`\r\n /// @param evidenceHash keccak256 of the canonical evidence document held off-chain\r\n event RiskVerdict(\r\n bytes32 indexed payloadHash,\r\n uint8 action,\r\n uint16 score,\r\n uint256 reasonMask,\r\n bytes32 evidenceHash\r\n );\r\n\r\n /// @dev Action codes. These are part of the event ABI: an indexer decoding old logs relies\r\n /// on them, so the numbering is permanent. Kept in sync with the worker's ACTION_CODES.\r\n uint8 public constant ACTION_ALLOW = 0;\r\n uint8 public constant ACTION_DELAY = 1;\r\n uint8 public constant ACTION_MANUAL_REVIEW = 2;\r\n uint8 public constant ACTION_BLOCK = 3;\r\n\r\n error NotOperator();\r\n error NotSendLibrary();\r\n error UnknownAction(uint8 action);\r\n /// @dev Submitting a verification asserts the packet was allowed; any other action would be\r\n /// a self-contradicting record.\r\n error VerificationRequiresAllow(uint8 action);\r\n /// @dev An allow rides along on `submitVerification`, so recording one separately would\r\n /// double-report the same outcome.\r\n error AllowNotSeparatelyRecorded();\r\n\r\n modifier onlyOperator() {\r\n if (msg.sender != operator) revert NotOperator();\r\n _;\r\n }\r\n\r\n constructor(\r\n address _owner,\r\n address _operator,\r\n address _sendUln,\r\n address _receiveUln,\r\n uint256 _fee\r\n ) Ownable(_owner) {\r\n require(_operator != address(0), \"zero operator\");\r\n require(_sendUln != address(0), \"zero sendUln\");\r\n require(_receiveUln != address(0), \"zero receiveUln\");\r\n operator = _operator;\r\n sendUln = _sendUln;\r\n receiveUln = _receiveUln;\r\n fee = _fee;\r\n }\r\n\r\n function getFee(\r\n uint32 /*_dstEid*/,\r\n uint64 /*_confirmations*/,\r\n address /*_sender*/,\r\n bytes calldata /*_options*/\r\n ) external view returns (uint256) {\r\n return fee;\r\n }\r\n\r\n function assignJob(AssignJobParam calldata _param, bytes calldata) external payable returns (uint256) {\r\n // Only the send library assigns jobs. The worker treats a JobAssigned payloadHash as\r\n // \"this packet is ours to screen\" and spends operator gas verifying it, so an open\r\n // assignJob would let anyone point the worker at packets no one asked it to verify.\r\n if (msg.sender != sendUln) revert NotSendLibrary();\r\n // NOTE: SendUln302 calls assignJob WITHOUT forwarding value (msg.value == 0); the\r\n // messagelib accrues each worker's fee internally and workers withdraw separately\r\n // (see SendUlnBase._assignJobs). So we must NOT require msg.value >= fee here — doing\r\n // so reverts every real send. We simply record the job and return our fee quote.\r\n emit JobAssigned(_param.dstEid, _param.payloadHash, _param.confirmations, _param.sender);\r\n return fee;\r\n }\r\n\r\n /// @notice Attest a packet and record the risk verdict that permitted it, in one call.\r\n /// @dev The verdict rides along at no extra transaction cost, so an allowed packet always\r\n /// carries an auditable reason for having been allowed. `action` must be ACTION_ALLOW:\r\n /// a packet that was blocked or held cannot also have been verified. An owner-approved\r\n /// release is reported as ACTION_ALLOW too — a human allowed it — with the reason mask\r\n /// still carrying why it had been held.\r\n function submitVerification(\r\n bytes calldata packetHeader,\r\n bytes32 payloadHash,\r\n uint64 confirmations,\r\n uint8 action,\r\n uint16 score,\r\n uint256 reasonMask,\r\n bytes32 evidenceHash\r\n ) external onlyOperator {\r\n if (action != ACTION_ALLOW) revert VerificationRequiresAllow(action);\r\n IReceiveUlnE2(receiveUln).verify(packetHeader, payloadHash, confirmations);\r\n emit RiskVerdict(payloadHash, action, score, reasonMask, evidenceHash);\r\n }\r\n\r\n /// @notice Record a verdict for a packet that was NOT verified.\r\n /// @dev Withholding the attestation is what actually stops the packet; this only leaves the\r\n /// audit trail. It is therefore best-effort by design — the worker treats a failure\r\n /// here as a lost record, never as a failure to enforce.\r\n function recordVerdict(\r\n bytes32 payloadHash,\r\n uint8 action,\r\n uint16 score,\r\n uint256 reasonMask,\r\n bytes32 evidenceHash\r\n ) external onlyOperator {\r\n if (action > ACTION_BLOCK) revert UnknownAction(action);\r\n if (action == ACTION_ALLOW) revert AllowNotSeparatelyRecorded();\r\n emit RiskVerdict(payloadHash, action, score, reasonMask, evidenceHash);\r\n }\r\n\r\n /// @notice Clear a packet the worker withheld for manual review.\r\n /// @dev Emits only; no storage. The worker observes `PacketApproved` and releases the\r\n /// packet from its local deferred queue. Approval is a human override of a risk\r\n /// verdict, so it is separated from the operator key by design — a compromised or\r\n /// buggy worker cannot approve the packets it chose to hold.\r\n function approvePacket(bytes32 payloadHash) external onlyOwner {\r\n emit PacketApproved(payloadHash, msg.sender);\r\n }\r\n\r\n function setOperator(address _operator) external onlyOwner {\r\n require(_operator != address(0), \"zero operator\");\r\n operator = _operator;\r\n emit OperatorSet(_operator);\r\n }\r\n\r\n function setSendUln(address _sendUln) external onlyOwner {\r\n require(_sendUln != address(0), \"zero sendUln\");\r\n sendUln = _sendUln;\r\n emit SendUlnSet(_sendUln);\r\n }\r\n\r\n function setReceiveUln(address _receiveUln) external onlyOwner {\r\n require(_receiveUln != address(0), \"zero receiveUln\");\r\n receiveUln = _receiveUln;\r\n emit ReceiveUlnSet(_receiveUln);\r\n }\r\n\r\n function setFee(uint256 _fee) external onlyOwner {\r\n fee = _fee;\r\n emit FeeSet(_fee);\r\n }\r\n\r\n function withdraw(address payable _to) external onlyOwner {\r\n (bool ok, ) = _to.call{ value: address(this).balance }(\"\");\r\n require(ok, \"withdraw failed\");\r\n }\r\n}\r\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "evmVersion": "paris", + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + "devdoc", + "userdoc", + "storageLayout", + "evm.gasEstimates" + ], + "": [ + "ast" + ] + } + }, + "metadata": { + "useLiteralContent": true + } + } +} \ No newline at end of file diff --git a/hardhat.config.ts b/hardhat.config.ts index fa300b5..a589664 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -20,6 +20,9 @@ const accounts = process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [] const config: HardhatUserConfig = { paths: { cache: 'cache/hardhat', + // Demo-only deploys (decoy tokens, risky proxy) live under demo/ so the main tree stays + // clean; hardhat-deploy still discovers them, and their tags select them individually. + deploy: ['deploy', 'demo/deploy'], }, solidity: { compilers: [ diff --git a/indexer/.dockerignore b/indexer/.dockerignore new file mode 100644 index 0000000..14f5e34 --- /dev/null +++ b/indexer/.dockerignore @@ -0,0 +1,6 @@ +node_modules +test +.env +.env.* +!.env.example +*.log diff --git a/indexer/.env.example b/indexer/.env.example new file mode 100644 index 0000000..fbb4c69 --- /dev/null +++ b/indexer/.env.example @@ -0,0 +1,94 @@ +# ── Compliance DVN indexer configuration ──────────────────────────────────── +# Copy to .env (gitignored) and fill in. Every value is validated at boot; the indexer refuses +# to start with an aggregated error rather than running half-configured. + +# ── Database ──────────────────────────────────────────────────────────────── +# docker compose builds this for the indexer container automatically; set these to change it. +POSTGRES_USER=indexer +POSTGRES_PASSWORD=indexer +POSTGRES_DB=indexer +# Only needed when running the indexer outside compose. +# DATABASE_URL=postgres://indexer:indexer@localhost:5432/indexer + +# ── Feed signing ──────────────────────────────────────────────────────────── +# REQUIRED. The key that signs published feeds (0x + 64 hex). This key NEVER goes to the worker; +# the worker only needs its ADDRESS, which you add to its INDEXER_SIGNERS allowlist. +FEED_SIGNING_KEY= + +# Identifies this publisher in the document. The worker tracks accepted versions per source, so +# two indexers must use different values or they will reject each other's feeds as replays. +FEED_SOURCE=trusted-indexer-a + +# How long a published document stays valid, and how often a new one is built. The TTL must +# exceed the rebuild interval, or a feed can expire before its replacement exists and the +# worker's screening will flap. +FEED_TTL_SEC=7200 +FEED_REBUILD_MS=600000 + +# Must equal the worker's POLICY_VERSION (worker/assess/policy.ts). A mismatch is rejected +# rather than reconciled — scores computed under different weights are not comparable. +POLICY_VERSION=2 + +# ── Chains ────────────────────────────────────────────────────────────────── +# REQUIRED per enabled chain: the deployed ComplianceDVN address, so its risk events can be read. +DVN_BASE_SEPOLIA= +DVN_OPTIMISM_SEPOLIA= + +RPC_URL_BASE_SEPOLIA=https://sepolia.base.org +RPC_URL_OPTIMISM_SEPOLIA=https://sepolia.optimism.io + +# Restrict the active chain set (comma-separated keys). Default: all known chains. +# CHAINS_ENABLED=baseSepolia,optimismSepolia + +# ── Graph ─────────────────────────────────────────────────────────────────── +# ERC-20 contracts whose Transfer events build the edge graph, comma-separated. EMPTY MEANS NO +# EDGES, so the feed will be empty — this is the setting that actually turns proximity on. +# Only ERC-20 transfers are collected; native-value transfers need trace APIs and are not read. +TRACKED_TOKENS= + +# ── Source verification ───────────────────────────────────────────────────── +# Where to ask whether a contract's source is verified, which cannot be read from chain state. +# Targets Sourcify's v2 API (`GET /v2/contract/{chainId}/{address}`) — v1 is in a scheduled +# brownout and returns 503. Point this at a self-hosted Sourcify to avoid the public rate limit. +# EMPTY DISABLES verification entirely: no unverified_contract labels are published. +VERIFIER_URL=https://sourcify.dev/server + +# Addresses resolved per chain per pass. v2 answers one address per request, so this bounds how +# hard the verifier is hit; a 429 stops the pass early and the rest are retried next time. +VERIFY_BATCH=50 + +# How long an answer is trusted before re-checking. A contract can become verified later, so this +# is not forever — but it changes rarely, hence a week. +VERIFY_TTL_SEC=604800 + +# ── Inbound thresholds (dusting defence) ──────────────────────────────────── +# Minimum transfer value for an INBOUND edge (sanctioned address -> subject) to count as +# exposure. Anyone can push a tainted transfer at a victim to poison their address, so an inbound +# edge below the minimum is recorded but never labelled. +# +# Format: chain:token:minValue (comma-separated). minValue is in the token's SMALLEST UNIT, not +# a decimal amount — for an 18-decimal token, 0.01 tokens is 10000000000000000. Getting the +# decimals wrong by a few orders of magnitude silently disables or over-triggers the threshold. +# +# TOKEN_MINIMUMS=baseSepolia:0xTOKEN:10000000000000000,optimismSepolia:0xTOKEN:10000 +# +# A token listed in TRACKED_TOKENS with no entry here NEVER produces an inbound label. That is +# deliberate — a permissive default would make every dust transfer evidence — but it does mean +# leaving this empty turns the sanctions_1hop_inbound signal off entirely. The indexer logs a +# warning at boot naming any tracked token that is missing a threshold. +# +# Outbound edges (subject -> sanctioned address) need no threshold and are unaffected: sending +# anything at all is the subject's own act. +TOKEN_MINIMUMS= + +# ── Scanning ──────────────────────────────────────────────────────────────── +POLL_MS=15000 # pause between scan passes +CONFIRMATIONS=5 # only read below head - CONFIRMATIONS, so reorgs are rare +SCAN_BACKFILL_BLOCKS=5000 # how far back to start on a cold cursor +SCAN_CHUNK_BLOCKS=2000 # maximum blocks per getLogs call +REORG_DEPTH=32 # how far to unwind on a reorg; deeper than this aborts loudly + +# ── Operational surface ───────────────────────────────────────────────────── +HTTP_PORT=9091 # serves /feed/latest.json /healthz /readyz /metrics +LOG_LEVEL=info # fatal|error|warn|info|debug|trace|silent +NODE_ENV=production # 'development' enables pretty (non-JSON) logs diff --git a/indexer/Dockerfile b/indexer/Dockerfile new file mode 100644 index 0000000..1a04ed2 --- /dev/null +++ b/indexer/Dockerfile @@ -0,0 +1,26 @@ +# syntax=docker/dockerfile:1 +FROM node:20-alpine AS base +WORKDIR /app +RUN corepack enable && corepack prepare pnpm@9.15.9 --activate + +FROM base AS deps +COPY package.json ./ +# No lockfile is committed for this package yet, so resolve fresh. Add pnpm-lock.yaml and switch +# to --frozen-lockfile once the dependency set settles. +RUN pnpm install --prod=false + +FROM base AS runtime +ENV NODE_ENV=production +COPY --from=deps /app/node_modules ./node_modules +COPY package.json tsconfig.json ./ +COPY db ./db +COPY src ./src + +# Unprivileged: the container needs nothing but outbound RPC and the database. +USER node +EXPOSE 9091 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ + CMD node -e "require('http').get('http://127.0.0.1:'+(process.env.HTTP_PORT||9091)+'/healthz',r=>process.exit(r.statusCode===200?0:1)).on('error',()=>process.exit(1))" + +CMD ["node_modules/.bin/tsx", "src/service.ts"] diff --git a/indexer/README.md b/indexer/README.md new file mode 100644 index 0000000..86bfc24 --- /dev/null +++ b/indexer/README.md @@ -0,0 +1,245 @@ +# External Indexer + +A third-party component, separate from the DVN worker. It collects on-chain risk events, +builds the graph, and publishes a **signed snapshot feed** that the worker ingests into its +local `RiskStore`. The worker never talks to this database. + +``` +Authoritative feeds + DVN events + ↓ + External Indexer (this folder) + ↓ +signed snapshot / delta feed + ↓ +DVN worker local RiskStore (worker/assess) + ↓ +LayerZero packet verification +``` + +## Boundaries + +Owns: `RiskVerdict` event collection, reorg handling, dedup, N-hop graph, exposure counts, +label propagation, long-term storage, dashboards, and **signing the published feed** (the +signing key lives here, never in the worker). + +Does not own: packet verification, `submitVerification`, or any enforcement decision. The +worker treats this as one source among several, subject to source trust level and signature +verification. + +## Run it + +```bash +cp .env.example .env # set FEED_SIGNING_KEY, the DVN addresses, and TRACKED_TOKENS +docker compose up -d +curl localhost:9091/feed/latest.json +``` + +Then point the worker at it: `INDEXER_FEED_URL=http://:9091/feed/latest.json` and add this +instance's signer address to the worker's `INDEXER_SIGNERS`. The signing key never leaves here — +the worker only ever needs the address. + +`TRACKED_TOKENS` is the setting that actually turns proximity on. Empty means no transfer edges +are collected, so the feed will be valid, signed, and empty. + +## Layout + +| Path | Contents | +| -------------------- | ---------------------------------------------------------- | +| `docker-compose.yml` | postgres 16 + indexer | +| `Dockerfile` | indexer image (unprivileged, healthchecked) | +| `db/migrations/` | schema: blocks, edges, seeds, verdicts, approvals, feeds | +| `src/config.ts` | env validation; fails with every problem at once | +| `src/chain/` | event decoding for RiskVerdict / PacketApproved / ERC-20 | +| `src/ingest/` | scan loop, reorg rollback, idempotent writes, seed refresh | +| `src/graph/` | one-hop proximity and exposure counts | +| `src/verify/` | Sourcify v2 client + verification refresh pass | +| `src/feed/` | snapshot builder + EIP-191 signer | +| `src/http/` | `/feed/latest.json`, `/healthz`, `/readyz`, `/metrics` | +| `deploy/` | Grafana dashboard (Prometheus + Postgres) | + +## How it works + +**Ingest.** Each chain is scanned only up to `head - CONFIRMATIONS`, in `SCAN_CHUNK_BLOCKS` +ranges, and every write is `ON CONFLICT DO NOTHING` on `(chain, tx_hash, log_index)` — so +re-scanning a range is idempotent and a rollback is safe rather than duplicating rows. The cursor +advances per committed chunk inside the same transaction as the rows. + +**Reorgs.** Detected by comparing the recorded hash for the cursor height against the node's +current hash for that height; block numbers alone cannot tell you the chain was rewritten. On a +mismatch the last `REORG_DEPTH` blocks are deleted and rescanned. Only touched blocks are +recorded, so rather than searching for the exact fork point (which sparse history would stop +short of), a bounded window is unwound wholesale. Whether the rewrite went deeper is answered +against a real anchor — the deepest block still on record below that window — and if that also +disagrees the scan aborts loudly instead of leaving stale rows underneath it. + +**Graph.** Depth 3 (`GRAPH_DEPTH`, matching the worker's `N_HOP.depth`), direction-aware, with a +label per depth (`sanctions_1hop/2hop/3hop`, …) so the worker can weight distance. A path counts +only if funds could have flowed along it: same chain, non-decreasing block order, no vertex twice, +no seed anywhere but the far endpoint, and the shortest route wins. The subject's own first +outbound edge needs no threshold; every other edge — outbound relays and all inbound hops — must +clear the per-token minimum, because anyone can push (or relay) a tainted transfer to poison an +address they do not control. + +Those minimums come from `TOKEN_MINIMUMS` and are **what turns the inbound signal on** — a token +with no entry is never labelled inbound, so leaving it empty means `sanctions_1hop_inbound` never +fires at all. The values are replaced wholesale on every boot (config is their only source, so +removing an entry must actually remove the threshold), and the indexer logs a warning naming any +tracked token that is missing one. + +Only ERC-20 `Transfer` events build edges — native-value transfers need trace APIs most public +RPCs do not expose, which is worth knowing when reading an exposure result. + +Raising depth past 1 is a policy decision, not a refactor: it changes what the DVN is willing to +refuse a transfer over, and a second hop needs its own weight and threshold since "two hops from a +sanctioned address" is much weaker evidence than one. + +**Seeds.** OFAC / OpenSanctions / mixer lists are loaded as the seed set for proximity and are +replaced per source on refresh, so a lifted sanction stops seeding. They are deliberately **not** +republished — see below. + +**Source verification.** Whether a contract's source is verified cannot be read from chain state, +so it comes from Sourcify's **v2** API (`GET /v2/contract/{chainId}/{address}`; v1 is in a +scheduled brownout and returns 503). Two stages, because asking a verifier about an EOA is wasted +budget: `getCode` first, then only contracts are looked up. Answers are cached for `VERIFY_TTL_SEC` +and each pass is capped at `VERIFY_BATCH` addresses, since v2 answers one address per request; a +429 stops the pass early and the remainder is retried. + +The status is deliberately **three-valued**, not two. `verified: false` produces the +`unverified_contract` label, but a request that failed — a 503, a 429, a timeout — is recorded as +NULL and retried. Treating "we could not find out" as "not verified" would let a verifier outage +label every contract in the graph, which is the kind of failure that quietly inflates every score +it touches. This is enforced in `refresh.ts` and pinned by tests. + +## What is deliberately not published + +- **The seed labels themselves.** The worker reads OFAC and OpenSanctions first-hand. Re-feeding + a `sanctions` label as a `trusted_indexer` claim would launder an authoritative source into a + derived one, and derived labels cannot cause a refusal. +- **`action`, `confidence`, `score`.** All three belong to the worker (`SOURCE_TRUST`, + `ACTION_THRESHOLDS`, `LABEL_WEIGHTS`). Asserting them here would move enforcement authority to + the indexer. +- **Our own `RiskVerdict` events.** They are collected into `risk_verdicts` for audit and + dashboards, but feeding our past verdicts back as labels is self-amplifying: a hold becomes + evidence for the next hold. Consuming them would need an explicit decay or provenance rule + that does not exist yet. + +## Feed format (the contract with the worker) + +The consumer already exists: `worker/assess/ingest/feed.ts`. Match it exactly — every rule below +is enforced there, and a feed that breaks one is rejected whole, not partially applied. + +```json +{ + "version": 128, + "generatedAt": 1782090000, + "expiresAt": 1782093600, + "source": "trusted-indexer-a", + "policyVersion": 1, + "entries": [{ "address": "0xabc…", "labels": ["sanctions_1hop", "mixer_exposure"] }], + "signature": "0x…" +} +``` + +An entry may also carry `score`, `subjectType`, and `evidenceHash`; the worker accepts all three. +This implementation emits none of them — see "what is deliberately not published" above. + +**Signing.** The signature is an EIP-191 `personal_sign` over the canonical JSON of the whole +document **minus** the `signature` field. Canonical means: object keys sorted, arrays left in +order, no whitespace. The worker recovers the signer and checks it against its allowlist, so +publish the signer address out of band. + +**Integers only.** Every numeric field must be an integer. Float formatting is not guaranteed to +round-trip identically across languages, so a feed carrying one may verify on this side and fail +on the worker's. This is why entries carry no `confidence` — how far to trust this feed is the +worker's judgement (`SOURCE_TRUST`), not ours to assert. + +**Unknown fields are signed too.** The worker preserves fields it does not recognise when +verifying, so adding one is backward-compatible. But note it will be _ignored_, not honoured. + +**`action` is not ours to set.** A per-entry `action` is parsed and deliberately discarded. The +worker decides actions from its own policy; honouring ours would hand it enforcement authority +that `SOURCE_TRUST` exists to withhold. + +**Derived labels cannot cause a refusal.** Graph-derived labels — `sanctions_1hop`, +`mixer_exposure`, and anything else outside the worker's `DIRECT_HIT_LABELS` — top out at +`manual-review` however high they score, and `score` cannot manufacture a direct hit either. This +is deliberate: an inference should route to a human, not freeze funds. If the indexer establishes +that an address genuinely IS sanctioned (not merely near one), publish `sanctions` — that is a +direct hit and will block. + +**Versioning.** `version` must strictly increase per `source`. The worker persists the highest +version it has accepted, so a replayed older feed is rejected even across a restart. Two +indexers publishing under different `source` names keep independent counters. + +**Expiry is the TTL.** `expiresAt` is stamped onto every entry, so the worker stops scoring these +labels the moment the document goes stale — no purge step, no risk of a forgotten label. Keep +`expiresAt` comfortably longer than the worker's refresh interval (default 30 min) or screening +will flap. Removals need nothing special: the worker rebuilds its store from scratch each +refresh, so an address dropped from the feed is simply absent next time. + +**`policyVersion`** must equal the worker's `POLICY_VERSION` (`worker/assess/policy.ts`). A +mismatch is rejected rather than reconciled — scores computed under different weights are not +comparable. Coordinate policy bumps across both sides. + +## Out of scope + +Hash provenance (hash chain / Merkle DAG over risk judgments) is **not** part of this design. +Feed integrity comes from the signature and the source allowlist, not from a provenance chain. + +Not built yet, in rough order of usefulness: native-value edges (they emit no event, so they need +trace APIs most public RPCs do not expose), and depth > 1 traversal and label propagation beyond +one hop (depth 1 is the agreed policy, not a gap). + +## Dashboard + +```bash +docker compose --profile observability up -d +``` + +Grafana on (anonymous viewer; admin password `GRAFANA_ADMIN_PASSWORD`, +default `admin`), Prometheus on . Both bind to loopback only — anonymous +access is fine there and would not be on a shared interface. The profile keeps them out of the +default `up`, so a plain deployment still runs just postgres + indexer. + +Provisioning wires everything up: two datasources with fixed uids (`dvn-prometheus`, +`dvn-postgres`) and **both** dashboards — this one and the worker's, bind-mounted straight from the +repo so they are always the committed version. Nothing to import by hand. Prometheus scrapes the +indexer over the compose network and the worker via `host.docker.internal:9090`, which assumes the +worker runs on the host; point `deploy/observability/prometheus.yml` elsewhere if it does not. + +The indexer dashboard needs both datasources because they answer different questions: Prometheus +has the operational series (scan lag, reorgs, feed age), while the audit trail and graph output live +only in Postgres and are deliberately not exported as metrics — they are records, not gauges. That +split is also why the two disagree after a restart: Prometheus counters reset with the process, so +`dvn_decisions_total` can show one decision while `risk_verdicts` still holds every one ever made. + +Four sections: **ingest health** (scan lag, reorgs, error rate, seed count), **published feed** +(version, entry count, age against `FEED_TTL_SEC`, build outcomes, history), **risk verdicts** (the +audit trail — verdicts by action over time, recent verdicts with reason masks, owner approvals, and +held packets with no approval yet), and **graph & verification** (labels by type, most-exposed +subjects, verification status, configured thresholds). + +Two panels are worth knowing how to read: + +- **Verification status** separates `unknown (retrying)` from `unverified`. Only the latter + produces a label; the former means we never got an answer. +- **Inbound thresholds configured** shows which tokens actually have a minimum. A tracked token + absent from that table is silently not producing inbound labels. + +Event rows carry a block number, not a timestamp, so anything plotted over time joins through +`blocks.block_time`. When editing those panels, compute the timestamp in a CTE and pass a bare +column to `$__timeGroupAlias` / `$__timeFilter`: Grafana matches macro arguments with `\([^)]*\)`, +so a `to_timestamp(...)` call inside a macro is truncated at its own closing paren and the panel +fails with `macro __timeGroup needs time column and interval`. + +## Tests + +```bash +pnpm test +``` + +The SQL runs against an in-memory Postgres rather than being mocked, so the proximity queries, +the numeric uint256 comparisons, and the dusting threshold are genuinely executed. `canonicalize` +is duplicated from `worker/assess/canonical.ts` on purpose (separate packages, separate Docker +builds) and `test/feed.spec.ts` pins its output against a fixture, so drift fails a test instead +of silently breaking every signature. diff --git a/indexer/db/migrations/001_init.sql b/indexer/db/migrations/001_init.sql new file mode 100644 index 0000000..80efb35 --- /dev/null +++ b/indexer/db/migrations/001_init.sql @@ -0,0 +1,100 @@ +-- Compliance DVN indexer — initial schema. +-- +-- Numeric columns use numeric(78,0) because token values are uint256, which does not fit a +-- bigint. Addresses and hashes are stored lowercased hex; every write goes through a helper +-- that normalizes, so queries never need to case-fold. + +-- Per-chain scan cursor. Advances only after a range is fully committed. +CREATE TABLE IF NOT EXISTS scan_cursor ( + chain text PRIMARY KEY, + last_block bigint NOT NULL +); + +-- Block identity, kept so a reorg can be detected by comparing parent hashes rather than +-- guessing from block numbers alone. +-- +-- `block_time` is the only wall-clock anchor in the schema: events carry a block number, not a +-- timestamp, so anything that plots the audit trail over time joins through here. +CREATE TABLE IF NOT EXISTS blocks ( + chain text NOT NULL, + number bigint NOT NULL, + hash text NOT NULL, + parent_hash text NOT NULL, + block_time bigint NOT NULL, + PRIMARY KEY (chain, number) +); + +-- ERC-20 transfer edges. The (chain, tx_hash, log_index) primary key IS the dedup: re-scanning +-- a range is idempotent. +CREATE TABLE IF NOT EXISTS edges ( + chain text NOT NULL, + block_number bigint NOT NULL, + tx_hash text NOT NULL, + log_index integer NOT NULL, + token text NOT NULL, + from_addr text NOT NULL, + to_addr text NOT NULL, + value numeric(78,0) NOT NULL, + PRIMARY KEY (chain, tx_hash, log_index) +); +CREATE INDEX IF NOT EXISTS edges_from_idx ON edges (from_addr); +CREATE INDEX IF NOT EXISTS edges_to_idx ON edges (to_addr); +CREATE INDEX IF NOT EXISTS edges_block_idx ON edges (chain, block_number); + +-- Authoritative labels the graph is seeded from (sanctioned addresses, sanctioned mixers). +CREATE TABLE IF NOT EXISTS seed_labels ( + subject text NOT NULL, + label text NOT NULL, + source text NOT NULL, + PRIMARY KEY (subject, label, source) +); + +-- Verdicts emitted by our own DVN. Collected for audit and dashboards. Deliberately NOT fed +-- back into published labels by default — see src/feed/builder.ts on self-amplification. +CREATE TABLE IF NOT EXISTS risk_verdicts ( + chain text NOT NULL, + block_number bigint NOT NULL, + tx_hash text NOT NULL, + log_index integer NOT NULL, + payload_hash text NOT NULL, + action smallint NOT NULL, + score integer NOT NULL, + reason_mask numeric(78,0) NOT NULL, + evidence_hash text NOT NULL, + PRIMARY KEY (chain, tx_hash, log_index) +); +CREATE INDEX IF NOT EXISTS risk_verdicts_payload_idx ON risk_verdicts (payload_hash); +CREATE INDEX IF NOT EXISTS risk_verdicts_block_idx ON risk_verdicts (chain, block_number); + +-- Owner approvals of held packets. +CREATE TABLE IF NOT EXISTS packet_approvals ( + chain text NOT NULL, + block_number bigint NOT NULL, + tx_hash text NOT NULL, + log_index integer NOT NULL, + payload_hash text NOT NULL, + approver text NOT NULL, + PRIMARY KEY (chain, tx_hash, log_index) +); +CREATE INDEX IF NOT EXISTS packet_approvals_block_idx ON packet_approvals (chain, block_number); + +-- Minimum transfer value, per token, for an INBOUND edge to count as exposure. This is the +-- dusting defence: anyone can push a tainted transfer at a victim, so an inbound edge below the +-- minimum is recorded but never labelled. A token with no row here is never labelled inbound. +CREATE TABLE IF NOT EXISTS token_minimums ( + chain text NOT NULL, + token text NOT NULL, + min_value numeric(78,0) NOT NULL, + PRIMARY KEY (chain, token) +); + +-- Every feed document published, so a consumer can be handed an older version and the +-- monotonic version counter survives a restart. +CREATE TABLE IF NOT EXISTS feeds ( + version bigint PRIMARY KEY, + generated_at bigint NOT NULL, + expires_at bigint NOT NULL, + policy_version integer NOT NULL, + entry_count integer NOT NULL, + document text NOT NULL +); diff --git a/indexer/db/migrations/002_contract_status.sql b/indexer/db/migrations/002_contract_status.sql new file mode 100644 index 0000000..2b1d5d0 --- /dev/null +++ b/indexer/db/migrations/002_contract_status.sql @@ -0,0 +1,21 @@ +-- Source-verification status per address. +-- +-- Verification status cannot be read from chain state, so it comes from an external verifier +-- (Sourcify by default). Cached here because the answer is near-static and the verifier should +-- not be queried once per feed build. +-- +-- `verified` is deliberately nullable and means three things, not two: +-- true — the verifier positively reported a source match +-- false — the verifier answered, and this address was not among the matches +-- NULL — we have not got an answer yet (never asked, or the request failed) +-- Only `false` produces an `unverified_contract` label. Treating NULL as unverified would let a +-- verifier outage label every contract in the graph. +CREATE TABLE IF NOT EXISTS contract_status ( + chain text NOT NULL, + address text NOT NULL, + is_contract boolean NOT NULL, + verified boolean, + checked_at bigint NOT NULL, + PRIMARY KEY (chain, address) +); +CREATE INDEX IF NOT EXISTS contract_status_pending_idx ON contract_status (chain, checked_at); diff --git a/indexer/db/migrations/003_bridge_edges.sql b/indexer/db/migrations/003_bridge_edges.sql new file mode 100644 index 0000000..98b2129 --- /dev/null +++ b/indexer/db/migrations/003_bridge_edges.sql @@ -0,0 +1,16 @@ +-- Cross-chain sends as first-class edges. +-- +-- An OFT send is a burn on the source and a mint on the destination, and the zero address is +-- excluded from every path (see graph/proximity.ts), so a bridged transfer previously left no +-- traversable edge at all. Worse, a send the DVN blocks never reaches the destination — our own +-- enforcement erased the evidence that would justify the next decision. +-- +-- The source chain's `OFTSent` (who sent, how much) joined to the endpoint's `PacketSent` (who it +-- was addressed to) reconstructs the true counterparty pair, whether or not the packet was +-- delivered. Those land here as ordinary edges, marked so an attempt is never mistaken for a +-- settled transfer. +ALTER TABLE edges ADD COLUMN IF NOT EXISTS kind text NOT NULL DEFAULT 'transfer'; + +-- Where the value was addressed, for a bridge edge whose `to_addr` lives on another chain. NULL +-- for a same-chain transfer. +ALTER TABLE edges ADD COLUMN IF NOT EXISTS dst_chain text; diff --git a/indexer/deploy/grafana-dashboard.json b/indexer/deploy/grafana-dashboard.json new file mode 100644 index 0000000..ab22a95 --- /dev/null +++ b/indexer/deploy/grafana-dashboard.json @@ -0,0 +1,721 @@ +{ + "title": "Compliance DVN — Indexer", + "uid": "compliance-dvn-indexer", + "tags": [ + "compliance-dvn", + "indexer" + ], + "timezone": "browser", + "schemaVersion": 39, + "refresh": "1m", + "time": { + "from": "now-24h", + "to": "now" + }, + "editable": true, + "description": "Ingest health, graph output, and the audit trail of risk verdicts. Prometheus panels come from the indexer's /metrics; the verdict and graph panels query its Postgres directly, since that data is the long-term record and is not exported as metrics.", + "templating": { + "list": [ + { + "name": "prom", + "label": "Prometheus", + "type": "datasource", + "query": "prometheus", + "current": { + "text": "DVN Prometheus", + "value": "dvn-prometheus" + }, + "hide": 0 + }, + { + "name": "pg", + "label": "Postgres (indexer)", + "type": "datasource", + "query": "grafana-postgresql-datasource", + "current": { + "text": "DVN Postgres", + "value": "dvn-postgres" + }, + "hide": 0 + } + ] + }, + "panels": [ + { + "id": 100, + "type": "row", + "title": "Ingest health", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "collapsed": false + }, + { + "id": 1, + "title": "Up", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 0, + "y": 1 + }, + "options": { + "colorMode": "background", + "graphMode": "none" + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "type": "value", + "options": { + "0": { + "text": "DOWN", + "color": "red" + } + } + }, + { + "type": "value", + "options": { + "1": { + "text": "UP", + "color": "green" + } + } + } + ] + }, + "overrides": [] + }, + "targets": [ + { + "expr": "max(indexer_up)", + "refId": "A" + } + ] + }, + { + "id": 2, + "title": "Scan lag (head - cursor)", + "type": "timeseries", + "description": "How far behind the safe head each chain is. A steadily growing line means ingest cannot keep up; a flat line at roughly CONFIRMATIONS is healthy.", + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "gridPos": { + "h": 8, + "w": 11, + "x": 3, + "y": 1 + }, + "fieldConfig": { + "defaults": { + "unit": "none" + }, + "overrides": [] + }, + "targets": [ + { + "expr": "indexer_chain_head_block - indexer_cursor_block", + "legendFormat": "{{chain}}", + "refId": "A" + } + ] + }, + { + "id": 3, + "title": "Reorgs", + "type": "timeseries", + "description": "Reorgs detected and blocks rolled back. A spike here calls every derived label from that window into question, because the rows behind them were rescanned.", + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "gridPos": { + "h": 8, + "w": 10, + "x": 14, + "y": 1 + }, + "targets": [ + { + "expr": "sum by (chain) (increase(indexer_reorgs_total[1h]))", + "legendFormat": "reorgs {{chain}}", + "refId": "A" + }, + { + "expr": "sum by (chain) (increase(indexer_reorg_blocks_unwound_total[1h]))", + "legendFormat": "blocks unwound {{chain}}", + "refId": "B" + } + ] + }, + { + "id": 4, + "title": "Scan errors", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 0, + "y": 5 + }, + "options": { + "colorMode": "background", + "graphMode": "area" + }, + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "orange", + "value": 1 + }, + { + "color": "red", + "value": 10 + } + ] + } + }, + "overrides": [] + }, + "targets": [ + { + "expr": "sum(increase(indexer_scan_errors_total[1h]))", + "refId": "A" + } + ] + }, + { + "id": 5, + "title": "Ingest rate", + "type": "timeseries", + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 9 + }, + "targets": [ + { + "expr": "sum by (chain) (rate(indexer_edges_ingested_total[5m]))", + "legendFormat": "edges {{chain}}", + "refId": "A" + }, + { + "expr": "sum by (chain) (rate(indexer_verdicts_ingested_total[5m]))", + "legendFormat": "verdicts {{chain}}", + "refId": "B" + }, + { + "expr": "sum by (chain) (rate(indexer_approvals_ingested_total[5m]))", + "legendFormat": "approvals {{chain}}", + "refId": "C" + } + ] + }, + { + "id": 6, + "title": "Seed labels held", + "type": "timeseries", + "description": "Authoritative sanctions/mixer seeds the graph grows from. A drop to zero means proximity is being computed against nothing.", + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 9 + }, + "targets": [ + { + "expr": "max(indexer_seed_labels)", + "legendFormat": "seeds", + "refId": "A" + }, + { + "expr": "sum(increase(indexer_seed_refresh_total{result=\"failure\"}[1h]))", + "legendFormat": "refresh failures (1h)", + "refId": "B" + } + ] + }, + { + "id": 101, + "type": "row", + "title": "Published feed", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 17 + }, + "collapsed": false + }, + { + "id": 7, + "title": "Feed version", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 0, + "y": 18 + }, + "options": { + "colorMode": "none", + "graphMode": "none" + }, + "targets": [ + { + "expr": "max(indexer_feed_version)", + "refId": "A" + } + ] + }, + { + "id": 8, + "title": "Feed entries", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 4, + "y": 18 + }, + "options": { + "colorMode": "none", + "graphMode": "area" + }, + "targets": [ + { + "expr": "max(indexer_feed_entries)", + "refId": "A" + } + ] + }, + { + "id": 9, + "title": "Feed age", + "type": "stat", + "description": "Seconds since the newest feed was generated. The worker stops scoring feed labels once a document expires, so this climbing past FEED_TTL_SEC means degraded screening.", + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 8, + "y": 18 + }, + "options": { + "colorMode": "background", + "graphMode": "area" + }, + "fieldConfig": { + "defaults": { + "unit": "s", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "orange", + "value": 3600 + }, + { + "color": "red", + "value": 7200 + } + ] + } + }, + "overrides": [] + }, + "targets": [ + { + "expr": "time() - max(indexer_feed_generated_at)", + "refId": "A" + } + ] + }, + { + "id": 10, + "title": "Feed builds", + "type": "timeseries", + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 18 + }, + "targets": [ + { + "expr": "sum by (result) (increase(indexer_feed_build_total[1h]))", + "legendFormat": "{{result}}", + "refId": "A" + } + ] + }, + { + "id": 11, + "title": "Feed history", + "type": "table", + "description": "Every document published. `version` must strictly increase — the worker rejects a repeat.", + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${pg}" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 22 + }, + "targets": [ + { + "format": "table", + "rawQuery": true, + "rawSql": "SELECT to_timestamp(generated_at) AS generated, version, entry_count, policy_version, to_timestamp(expires_at) AS expires FROM feeds ORDER BY version DESC LIMIT 50", + "refId": "A" + } + ] + }, + { + "id": 102, + "type": "row", + "title": "Risk verdicts (audit trail)", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 30 + }, + "collapsed": false + }, + { + "id": 12, + "title": "Verdicts by action", + "type": "timeseries", + "description": "Decoded from the RiskVerdict event's action code: 0 allow, 1 delay, 2 manual-review, 3 block. Time comes from the block the event was in — events carry a block number, not a timestamp, so this joins through `blocks`. Note that delay and manual-review only appear if the worker's EMIT_VERDICT_EVENTS includes them.", + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${pg}" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 31 + }, + "targets": [ + { + "format": "time_series", + "rawQuery": true, + "rawSql": "WITH verdicts AS (SELECT to_timestamp(b.block_time) AS ts, v.action FROM risk_verdicts v JOIN blocks b ON b.chain = v.chain AND b.number = v.block_number) SELECT $__timeGroupAlias(ts, '1h'), count(*) AS value, CASE action WHEN 0 THEN 'allow' WHEN 1 THEN 'delay' WHEN 2 THEN 'manual-review' WHEN 3 THEN 'block' ELSE 'unknown' END AS metric FROM verdicts WHERE $__timeFilter(ts) GROUP BY 1, 3 ORDER BY 1", + "refId": "A" + } + ] + }, + { + "id": 13, + "title": "Verdict totals by action", + "type": "piechart", + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${pg}" + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 12, + "y": 31 + }, + "targets": [ + { + "format": "table", + "rawQuery": true, + "rawSql": "SELECT CASE action WHEN 0 THEN 'allow' WHEN 1 THEN 'delay' WHEN 2 THEN 'manual-review' WHEN 3 THEN 'block' ELSE 'unknown' END AS action, count(*) AS verdicts FROM risk_verdicts GROUP BY 1 ORDER BY 2 DESC", + "refId": "A" + } + ] + }, + { + "id": 14, + "title": "Owner approvals", + "type": "stat", + "description": "Held packets released by an owner approvePacket call. Each one is a human overriding a hold.", + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${pg}" + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 18, + "y": 31 + }, + "options": { + "colorMode": "value", + "graphMode": "none" + }, + "targets": [ + { + "format": "table", + "rawQuery": true, + "rawSql": "SELECT count(*) AS approvals FROM packet_approvals", + "refId": "A" + } + ] + }, + { + "id": 15, + "title": "Recent verdicts", + "type": "table", + "description": "reason_mask is a bitmask; bit assignments live in worker/assess/verdict.ts REASON_BITS and are append-only. evidence_hash commits to the off-chain evidence document.", + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${pg}" + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 39 + }, + "targets": [ + { + "format": "table", + "rawQuery": true, + "rawSql": "SELECT to_timestamp(b.block_time) AS time, v.chain, v.block_number, v.payload_hash, CASE v.action WHEN 0 THEN 'allow' WHEN 1 THEN 'delay' WHEN 2 THEN 'manual-review' WHEN 3 THEN 'block' ELSE 'unknown' END AS action, v.score, v.reason_mask, v.evidence_hash, v.tx_hash FROM risk_verdicts v JOIN blocks b ON b.chain = v.chain AND b.number = v.block_number ORDER BY v.block_number DESC LIMIT 100", + "refId": "A" + } + ] + }, + { + "id": 16, + "title": "Recent approvals", + "type": "table", + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${pg}" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 49 + }, + "targets": [ + { + "format": "table", + "rawQuery": true, + "rawSql": "SELECT to_timestamp(b.block_time) AS time, a.chain, a.block_number, a.payload_hash, a.approver, a.tx_hash FROM packet_approvals a JOIN blocks b ON b.chain = a.chain AND b.number = a.block_number ORDER BY a.block_number DESC LIMIT 50", + "refId": "A" + } + ] + }, + { + "id": 17, + "title": "Verdicts without a matching approval", + "type": "table", + "description": "Packets recorded as held (delay or manual-review) with no approval seen. These are the ones still waiting on a human.", + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${pg}" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 49 + }, + "targets": [ + { + "format": "table", + "rawQuery": true, + "rawSql": "SELECT to_timestamp(b.block_time) AS held_since, v.chain, v.block_number, v.payload_hash, v.score, v.reason_mask FROM risk_verdicts v JOIN blocks b ON b.chain = v.chain AND b.number = v.block_number LEFT JOIN packet_approvals a ON a.payload_hash = v.payload_hash WHERE v.action IN (1, 2) AND a.payload_hash IS NULL ORDER BY v.block_number DESC LIMIT 50", + "refId": "A" + } + ] + }, + { + "id": 103, + "type": "row", + "title": "Graph & verification", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 57 + }, + "collapsed": false + }, + { + "id": 18, + "title": "Feed watchlist (latest published feed)", + "type": "table", + "description": "Entries of the newest signed feed — the labels the worker actually ingests.", + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${pg}" + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 58 + }, + "targets": [ + { + "format": "table", + "rawQuery": true, + "rawSql": "SELECT e->>'address' AS address,\n array_to_string(ARRAY(SELECT json_array_elements_text(e->'labels')), ', ') AS labels\nFROM (SELECT json_array_elements((document::json)->'entries') AS e\n FROM feeds ORDER BY version DESC LIMIT 1) t\nORDER BY 1", + "refId": "A" + } + ] + }, + { + "id": 19, + "title": "Most exposed subjects", + "type": "table", + "description": "Distinct seeds each subject touched. Outbound counts at any value; inbound only above the configured token minimum.", + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${pg}" + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 58 + }, + "targets": [ + { + "format": "table", + "rawQuery": true, + "rawSql": "SELECT subject, count(DISTINCT seed) AS seeds FROM (SELECT e.from_addr AS subject, e.to_addr AS seed FROM edges e JOIN seed_labels s ON s.subject = e.to_addr UNION SELECT e.to_addr AS subject, e.from_addr AS seed FROM edges e JOIN seed_labels s ON s.subject = e.from_addr JOIN token_minimums m ON m.chain = e.chain AND m.token = e.token WHERE e.value >= m.min_value) t GROUP BY subject ORDER BY seeds DESC LIMIT 25", + "refId": "A" + } + ] + }, + { + "id": 20, + "title": "Source verification", + "type": "table", + "description": "verified NULL means we never got an answer from the verifier, which is NOT the same as unverified — only `false` produces an unverified_contract label.", + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${pg}" + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 58 + }, + "targets": [ + { + "format": "table", + "rawQuery": true, + "rawSql": "SELECT chain, CASE WHEN is_contract = false THEN 'eoa' WHEN verified IS NULL THEN 'unknown (retrying)' WHEN verified THEN 'verified' ELSE 'unverified' END AS status, count(*) AS addresses FROM contract_status GROUP BY 1, 2 ORDER BY 1, 3 DESC", + "refId": "A" + } + ] + }, + { + "id": 21, + "title": "Inbound thresholds configured", + "type": "table", + "description": "A tracked token missing from this table never produces sanctions_1hop_inbound — its inbound edges are recorded but never labelled.", + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${pg}" + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 0, + "y": 66 + }, + "targets": [ + { + "format": "table", + "rawQuery": true, + "rawSql": "SELECT m.chain, m.token, m.min_value, count(e.tx_hash) AS edges_seen FROM token_minimums m LEFT JOIN edges e ON e.chain = m.chain AND e.token = m.token GROUP BY 1, 2, 3 ORDER BY 1, 2", + "refId": "A" + } + ] + }, + { + "id": 22, + "title": "Unverified contracts found", + "type": "timeseries", + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 12, + "y": 66 + }, + "targets": [ + { + "expr": "indexer_verification_unverified", + "legendFormat": "unverified {{chain}}", + "refId": "A" + }, + { + "expr": "sum by (chain) (rate(indexer_verification_checked_total[15m]))", + "legendFormat": "checked/s {{chain}}", + "refId": "B" + } + ] + } + ] +} diff --git a/indexer/deploy/grafana-demo-dashboard.json b/indexer/deploy/grafana-demo-dashboard.json new file mode 100644 index 0000000..f9e3204 --- /dev/null +++ b/indexer/deploy/grafana-demo-dashboard.json @@ -0,0 +1,396 @@ +{ + "title": "Compliance DVN — Demo", + "uid": "compliance-dvn-demo", + "tags": [ + "compliance-dvn", + "demo" + ], + "schemaVersion": 39, + "version": 1, + "timezone": "", + "refresh": "30s", + "time": { + "from": "now-7d", + "to": "now" + }, + "templating": { + "list": [ + { + "name": "prom", + "label": "Prometheus", + "type": "datasource", + "query": "prometheus", + "current": { + "text": "DVN Prometheus", + "value": "dvn-prometheus" + }, + "hide": 0 + }, + { + "name": "pg", + "label": "Postgres (indexer)", + "type": "datasource", + "query": "grafana-postgresql-datasource", + "current": { + "text": "DVN Postgres", + "value": "dvn-postgres" + }, + "hide": 0 + } + ] + }, + "panels": [ + { + "id": 1, + "title": "Sanctions seeds (indexer)", + "type": "stat", + "description": "OFAC + OpenSanctions + curated mixers, refreshed on the feed cadence.", + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${pg}" + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 0, + "y": 0 + }, + "targets": [ + { + "format": "table", + "rawQuery": true, + "rawSql": "SELECT count(*) AS seeds FROM seed_labels", + "refId": "A" + } + ] + }, + { + "id": 2, + "title": "Seeds by label", + "type": "piechart", + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${pg}" + }, + "gridPos": { + "h": 6, + "w": 5, + "x": 4, + "y": 0 + }, + "targets": [ + { + "format": "table", + "rawQuery": true, + "rawSql": "SELECT label, count(*) AS n FROM seed_labels GROUP BY label", + "refId": "A" + } + ] + }, + { + "id": 3, + "title": "Worker denylist by source (live)", + "type": "piechart", + "description": "What the worker screens against right now. Empty until the worker (pnpm start) is running.", + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "gridPos": { + "h": 6, + "w": 5, + "x": 9, + "y": 0 + }, + "targets": [ + { + "expr": "dvn_denylist_size", + "legendFormat": "{{source}}", + "refId": "A" + } + ] + }, + { + "id": 4, + "title": "Feed version", + "type": "stat", + "description": "", + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "gridPos": { + "h": 6, + "w": 3, + "x": 14, + "y": 0 + }, + "targets": [ + { + "expr": "max(indexer_feed_version)", + "legendFormat": "", + "refId": "A" + } + ] + }, + { + "id": 5, + "title": "Feed entries", + "type": "stat", + "description": "Graph-derived watchlist entries currently served to the worker.", + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "gridPos": { + "h": 6, + "w": 3, + "x": 17, + "y": 0 + }, + "targets": [ + { + "expr": "max(indexer_feed_entries)", + "legendFormat": "", + "refId": "A" + } + ] + }, + { + "id": 6, + "title": "Worker READY", + "type": "stat", + "description": "1 = verifying. 0/no data = worker down or HALTED (fail-closed).", + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 20, + "y": 0 + }, + "targets": [ + { + "expr": "max(dvn_ready)", + "legendFormat": "", + "refId": "A" + } + ] + }, + { + "id": 7, + "title": "Sanctions & mixer seed list", + "type": "table", + "description": "The seed set the graph is grown from. The worker additionally holds these first-hand.", + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${pg}" + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 6 + }, + "targets": [ + { + "format": "table", + "rawQuery": true, + "rawSql": "SELECT subject AS address, label, source FROM seed_labels ORDER BY label, subject", + "refId": "A" + } + ] + }, + { + "id": 8, + "title": "Feed watchlist (labels served to the worker)", + "type": "table", + "description": "Entries of the latest signed feed: addresses within 3 hops of a seed, plus unverified contracts.", + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${pg}" + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 6 + }, + "targets": [ + { + "format": "table", + "rawQuery": true, + "rawSql": "SELECT e->>'address' AS address,\n array_to_string(ARRAY(SELECT json_array_elements_text(e->'labels')), ', ') AS labels\nFROM (SELECT json_array_elements((document::json)->'entries') AS e\n FROM feeds ORDER BY version DESC LIMIT 1) t\nORDER BY 1", + "refId": "A" + } + ] + }, + { + "id": 9, + "title": "Verdict log — action, score, decoded reasons", + "type": "table", + "description": "Every on-chain RiskVerdict, with the reason bitmask decoded back to labels.", + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${pg}" + }, + "gridPos": { + "h": 10, + "w": 14, + "x": 0, + "y": 15 + }, + "targets": [ + { + "format": "table", + "rawQuery": true, + "rawSql": "SELECT to_timestamp(b.block_time) AS time, v.chain,\n CASE v.action WHEN 0 THEN 'allow' WHEN 1 THEN 'delay' WHEN 2 THEN 'manual-review' WHEN 3 THEN 'block' END AS action, v.score,\n NULLIF(concat_ws(', ',\n CASE WHEN mod(div(v.reason_mask, 1::numeric), 2) = 1 THEN 'sanctions' END,\n CASE WHEN mod(div(v.reason_mask, 2::numeric), 2) = 1 THEN 'sanctioned_mixer' END,\n CASE WHEN mod(div(v.reason_mask, 4::numeric), 2) = 1 THEN 'scam_token' END,\n CASE WHEN mod(div(v.reason_mask, 8::numeric), 2) = 1 THEN 'operator_deny' END,\n CASE WHEN mod(div(v.reason_mask, 16::numeric), 2) = 1 THEN 'sanctions_1hop' END,\n CASE WHEN mod(div(v.reason_mask, 32::numeric), 2) = 1 THEN 'sanctions_1hop_inbound' END,\n CASE WHEN mod(div(v.reason_mask, 64::numeric), 2) = 1 THEN 'mixer_exposure' END,\n CASE WHEN mod(div(v.reason_mask, 128::numeric), 2) = 1 THEN 'fake_stablecoin_suspect' END,\n CASE WHEN mod(div(v.reason_mask, 256::numeric), 2) = 1 THEN 'honeypot_suspect' END,\n CASE WHEN mod(div(v.reason_mask, 512::numeric), 2) = 1 THEN 'contract_admin_risk' END,\n CASE WHEN mod(div(v.reason_mask, 1024::numeric), 2) = 1 THEN 'unverified_contract' END,\n CASE WHEN mod(div(v.reason_mask, 2048::numeric), 2) = 1 THEN 'upgradeable_proxy' END,\n CASE WHEN mod(div(v.reason_mask, 4096::numeric), 2) = 1 THEN 'contract_check_unavailable' END,\n CASE WHEN mod(div(v.reason_mask, 8192::numeric), 2) = 1 THEN 'token_check_unavailable' END,\n CASE WHEN mod(div(v.reason_mask, 16384::numeric), 2) = 1 THEN 'owner_approved' END,\n CASE WHEN mod(div(v.reason_mask, 32768::numeric), 2) = 1 THEN 'sanctions_2hop' END,\n CASE WHEN mod(div(v.reason_mask, 65536::numeric), 2) = 1 THEN 'sanctions_3hop' END,\n CASE WHEN mod(div(v.reason_mask, 131072::numeric), 2) = 1 THEN 'sanctions_2hop_inbound' END,\n CASE WHEN mod(div(v.reason_mask, 262144::numeric), 2) = 1 THEN 'sanctions_3hop_inbound' END,\n CASE WHEN mod(div(v.reason_mask, 524288::numeric), 2) = 1 THEN 'mixer_exposure_2hop' END,\n CASE WHEN mod(div(v.reason_mask, 1048576::numeric), 2) = 1 THEN 'mixer_exposure_3hop' END), '') AS reasons,\n v.payload_hash, v.tx_hash\nFROM risk_verdicts v\nJOIN blocks b ON b.chain = v.chain AND b.number = v.block_number\nORDER BY b.block_time DESC LIMIT 50", + "refId": "A" + } + ] + }, + { + "id": 10, + "title": "Verdicts by action", + "type": "piechart", + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${pg}" + }, + "gridPos": { + "h": 10, + "w": 5, + "x": 14, + "y": 15 + }, + "targets": [ + { + "format": "table", + "rawQuery": true, + "rawSql": "SELECT CASE action WHEN 0 THEN 'allow' WHEN 1 THEN 'delay' WHEN 2 THEN 'manual-review' WHEN 3 THEN 'block' END AS action, count(*) AS n FROM risk_verdicts GROUP BY action", + "refId": "A" + } + ] + }, + { + "id": 11, + "title": "Blocked (veto)", + "type": "stat", + "description": "Packets refused outright. Withholding verification IS the enforcement.", + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${pg}" + }, + "gridPos": { + "h": 5, + "w": 5, + "x": 19, + "y": 15 + }, + "targets": [ + { + "format": "table", + "rawQuery": true, + "rawSql": "SELECT count(*) AS blocked FROM risk_verdicts WHERE action = 3", + "refId": "A" + } + ] + }, + { + "id": 12, + "title": "Awaiting owner approval", + "type": "stat", + "description": "manual-review verdicts with no PacketApproved yet. Release with: dvn-cli approve", + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${pg}" + }, + "gridPos": { + "h": 5, + "w": 5, + "x": 19, + "y": 20 + }, + "targets": [ + { + "format": "table", + "rawQuery": true, + "rawSql": "SELECT count(DISTINCT v.payload_hash) AS awaiting FROM risk_verdicts v\nWHERE v.action = 2 AND NOT EXISTS\n (SELECT 1 FROM packet_approvals a WHERE a.payload_hash = v.payload_hash)", + "refId": "A" + } + ] + }, + { + "id": 13, + "title": "Owner approvals", + "type": "table", + "description": "Human releases of held packets — the owner key, never the worker's.", + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${pg}" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 25 + }, + "targets": [ + { + "format": "table", + "rawQuery": true, + "rawSql": "SELECT to_timestamp(b.block_time) AS time, a.chain, a.payload_hash, a.approver, a.tx_hash\nFROM packet_approvals a JOIN blocks b ON b.chain = a.chain AND b.number = a.block_number\nORDER BY b.block_time DESC LIMIT 20", + "refId": "A" + } + ] + }, + { + "id": 14, + "title": "Decisions by action (worker live)", + "type": "timeseries", + "description": "", + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 12, + "y": 25 + }, + "targets": [ + { + "expr": "sum by (action) (rate(dvn_decisions_total[5m]))", + "legendFormat": "{{action}}", + "refId": "A" + } + ] + }, + { + "id": 15, + "title": "Risk evidence observed (worker live)", + "type": "timeseries", + "description": "Which risk signals actually fired while screening packets.", + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 18, + "y": 25 + }, + "targets": [ + { + "expr": "sum by (type) (increase(dvn_screening_evidence_total[15m]))", + "legendFormat": "{{type}}", + "refId": "A" + } + ] + } + ] +} diff --git a/indexer/deploy/observability/grafana/provisioning/dashboards/dashboards.yml b/indexer/deploy/observability/grafana/provisioning/dashboards/dashboards.yml new file mode 100644 index 0000000..2488713 --- /dev/null +++ b/indexer/deploy/observability/grafana/provisioning/dashboards/dashboards.yml @@ -0,0 +1,12 @@ +apiVersion: 1 + +providers: + - name: compliance-dvn + orgId: 1 + type: file + # Read-only in the UI: the dashboards live in the repo, so an edit made in Grafana would be + # silently lost on the next container restart. + allowUiUpdates: false + options: + path: /var/lib/grafana/dashboards + foldersFromFilesStructure: false diff --git a/indexer/deploy/observability/grafana/provisioning/datasources/datasources.yml b/indexer/deploy/observability/grafana/provisioning/datasources/datasources.yml new file mode 100644 index 0000000..a5a0e76 --- /dev/null +++ b/indexer/deploy/observability/grafana/provisioning/datasources/datasources.yml @@ -0,0 +1,28 @@ +# Datasources are given FIXED uids so the dashboards can pin their datasource variables to them. +# Without that, a datasource-type variable loads empty and every panel reads "No data" until the +# operator picks one by hand — which looks exactly like a broken dashboard. +apiVersion: 1 + +datasources: + - name: DVN Prometheus + uid: dvn-prometheus + type: prometheus + access: proxy + url: http://prometheus:9090 + isDefault: true + editable: false + + - name: DVN Postgres + uid: dvn-postgres + type: grafana-postgresql-datasource + access: proxy + url: postgres:5432 + user: ${POSTGRES_USER} + database: ${POSTGRES_DB} + secureJsonData: + password: ${POSTGRES_PASSWORD} + jsonData: + # Local compose network, no TLS between containers. + sslmode: disable + postgresVersion: 1600 + editable: false diff --git a/indexer/deploy/observability/prometheus.yml b/indexer/deploy/observability/prometheus.yml new file mode 100644 index 0000000..ed2e3d8 --- /dev/null +++ b/indexer/deploy/observability/prometheus.yml @@ -0,0 +1,26 @@ +# Scrape config for the local observability stack. +# +# Two targets on different sides of the container boundary: +# - the indexer runs in this compose project, so it is reachable by service name +# - the worker runs on the HOST (pnpm start), so it is reachable via host.docker.internal +# +# `host-gateway` is mapped in docker-compose.yml so this also works outside Docker Desktop. +global: + scrape_interval: 15s + evaluation_interval: 15s + +scrape_configs: + - job_name: indexer + static_configs: + - targets: ['indexer:9091'] + labels: + component: indexer + + - job_name: worker + static_configs: + # The worker's HTTP_PORT. If you run the worker in a container instead, change this to its + # service name — a target that never comes up shows as "down" in Prometheus rather than + # failing anything, which is easy to miss. + - targets: ['host.docker.internal:9090'] + labels: + component: worker diff --git a/indexer/docker-compose.yml b/indexer/docker-compose.yml new file mode 100644 index 0000000..5b24762 --- /dev/null +++ b/indexer/docker-compose.yml @@ -0,0 +1,113 @@ +# Compliance DVN indexer: PostgreSQL + the indexer service. +# +# cp .env.example .env # fill in FEED_SIGNING_KEY and the DVN addresses +# docker compose up -d +# +# The feed is then served at http://localhost:9091/feed/latest.json — point the worker's +# INDEXER_FEED_URL at it and add this instance's signer address to INDEXER_SIGNERS. + +services: + postgres: + image: postgres:16-alpine + restart: unless-stopped + environment: + POSTGRES_USER: ${POSTGRES_USER:-indexer} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-indexer} + POSTGRES_DB: ${POSTGRES_DB:-indexer} + volumes: + - pgdata:/var/lib/postgresql/data + # Not published by default: only the indexer needs it. Uncomment to inspect with psql. + # ports: + # - '5432:5432' + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER:-indexer} -d ${POSTGRES_DB:-indexer}'] + interval: 10s + timeout: 5s + retries: 5 + + indexer: + build: . + restart: unless-stopped + depends_on: + # Wait for readiness, not just for the container to exist — migrations run at boot. + postgres: + condition: service_healthy + environment: + DATABASE_URL: postgres://${POSTGRES_USER:-indexer}:${POSTGRES_PASSWORD:-indexer}@postgres:5432/${POSTGRES_DB:-indexer} + FEED_SIGNING_KEY: ${FEED_SIGNING_KEY:?FEED_SIGNING_KEY is required} + FEED_SOURCE: ${FEED_SOURCE:-trusted-indexer-a} + FEED_TTL_SEC: ${FEED_TTL_SEC:-7200} + FEED_REBUILD_MS: ${FEED_REBUILD_MS:-600000} + POLICY_VERSION: ${POLICY_VERSION:-2} + CHAINS_ENABLED: ${CHAINS_ENABLED:-} + DVN_BASE_SEPOLIA: ${DVN_BASE_SEPOLIA:-} + DVN_OPTIMISM_SEPOLIA: ${DVN_OPTIMISM_SEPOLIA:-} + RPC_URL_BASE_SEPOLIA: ${RPC_URL_BASE_SEPOLIA:-} + RPC_URL_OPTIMISM_SEPOLIA: ${RPC_URL_OPTIMISM_SEPOLIA:-} + TRACKED_TOKENS: ${TRACKED_TOKENS:-} + VERIFIER_URL: ${VERIFIER_URL-https://sourcify.dev/server} + VERIFY_BATCH: ${VERIFY_BATCH:-50} + VERIFY_TTL_SEC: ${VERIFY_TTL_SEC:-604800} + TOKEN_MINIMUMS: ${TOKEN_MINIMUMS:-} + POLL_MS: ${POLL_MS:-15000} + CONFIRMATIONS: ${CONFIRMATIONS:-5} + SCAN_BACKFILL_BLOCKS: ${SCAN_BACKFILL_BLOCKS:-5000} + SCAN_CHUNK_BLOCKS: ${SCAN_CHUNK_BLOCKS:-2000} + REORG_DEPTH: ${REORG_DEPTH:-32} + HTTP_PORT: 9091 + LOG_LEVEL: ${LOG_LEVEL:-info} + NODE_ENV: ${NODE_ENV:-production} + ports: + - '${HTTP_PORT:-9091}:9091' + + # ── Observability, opt-in ───────────────────────────────────────────────── + # + # docker compose --profile observability up -d + # + # Behind a profile so the default `up` stays just postgres + indexer. Both services bind to + # 127.0.0.1 only: Grafana runs with anonymous viewer access for convenience, which is fine on + # loopback and would not be on a shared interface. + prometheus: + image: prom/prometheus:v3.1.0 + profiles: ['observability'] + restart: unless-stopped + volumes: + - ./deploy/observability/prometheus.yml:/etc/prometheus/prometheus.yml:ro + - promdata:/prometheus + # The worker runs on the host, not in this project, so the container needs a route to it. + extra_hosts: + - 'host.docker.internal:host-gateway' + ports: + - '127.0.0.1:${PROMETHEUS_PORT:-9092}:9090' + + grafana: + image: grafana/grafana:11.4.0 + profiles: ['observability'] + restart: unless-stopped + depends_on: + - prometheus + environment: + # Datasource provisioning interpolates these, so Grafana needs the same DB credentials. + POSTGRES_USER: ${POSTGRES_USER:-indexer} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-indexer} + POSTGRES_DB: ${POSTGRES_DB:-indexer} + GF_AUTH_ANONYMOUS_ENABLED: 'true' + GF_AUTH_ANONYMOUS_ORG_ROLE: Viewer + GF_AUTH_DISABLE_LOGIN_FORM: 'false' + GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:-admin} + GF_USERS_DEFAULT_THEME: dark + volumes: + - ./deploy/observability/grafana/provisioning:/etc/grafana/provisioning:ro + # Both dashboards are mounted straight from the repo, so they are always the committed + # version rather than a copy that has drifted in Grafana's own database. + - ./deploy/grafana-dashboard.json:/var/lib/grafana/dashboards/indexer.json:ro + - ./deploy/grafana-demo-dashboard.json:/var/lib/grafana/dashboards/demo.json:ro + - ../worker/deploy/grafana-dashboard.json:/var/lib/grafana/dashboards/worker.json:ro + - grafanadata:/var/lib/grafana + ports: + - '127.0.0.1:${GRAFANA_PORT:-3000}:3000' + +volumes: + pgdata: + promdata: + grafanadata: diff --git a/indexer/package.json b/indexer/package.json new file mode 100644 index 0000000..f6289b7 --- /dev/null +++ b/indexer/package.json @@ -0,0 +1,36 @@ +{ + "name": "compliance-dvn-indexer", + "version": "1.0.0", + "private": true, + "description": "External indexer for the Compliance DVN — collects risk events, builds the graph, publishes a signed feed", + "license": "MIT", + "packageManager": "pnpm@9.15.9", + "engines": { + "node": ">=20" + }, + "scripts": { + "start": "tsx src/service.ts", + "migrate": "tsx src/migrate.ts", + "test": "vitest run test", + "test:watch": "vitest test", + "typecheck": "tsc --noEmit -p tsconfig.json" + }, + "dependencies": { + "dotenv": "^17.4.2", + "ethers": "^5.7.2", + "node-fetch": "^3.3.2", + "pg": "^8.13.1", + "pino": "^10.3.1", + "prom-client": "^15.1.3", + "tsx": "^4.22.4", + "zod": "^4.4.3" + }, + "devDependencies": { + "@types/node": "^25.9.3", + "@types/pg": "^8.11.10", + "pg-mem": "^3.0.5", + "pino-pretty": "^13.1.3", + "typescript": "^5.4.4", + "vitest": "^4.1.8" + } +} diff --git a/indexer/pnpm-lock.yaml b/indexer/pnpm-lock.yaml new file mode 100644 index 0000000..7db9630 --- /dev/null +++ b/indexer/pnpm-lock.yaml @@ -0,0 +1,2257 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + dotenv: + specifier: ^17.4.2 + version: 17.4.2 + ethers: + specifier: ^5.7.2 + version: 5.8.0 + node-fetch: + specifier: ^3.3.2 + version: 3.3.2 + pg: + specifier: ^8.13.1 + version: 8.20.0 + pino: + specifier: ^10.3.1 + version: 10.3.1 + prom-client: + specifier: ^15.1.3 + version: 15.1.3 + tsx: + specifier: ^4.22.4 + version: 4.23.1 + zod: + specifier: ^4.4.3 + version: 4.4.3 + devDependencies: + '@types/node': + specifier: ^25.9.3 + version: 25.9.5 + '@types/pg': + specifier: ^8.11.10 + version: 8.20.0 + pg-mem: + specifier: ^3.0.5 + version: 3.0.14 + pino-pretty: + specifier: ^13.1.3 + version: 13.1.3 + typescript: + specifier: ^5.4.4 + version: 5.9.3 + vitest: + specifier: ^4.1.8 + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.9.5)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(tsx@4.23.1)) + +packages: + + '@emnapi/core@1.11.1': + resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + + '@emnapi/runtime@1.11.1': + resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} + + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@ethersproject/abi@5.8.0': + resolution: {integrity: sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q==} + + '@ethersproject/abstract-provider@5.8.0': + resolution: {integrity: sha512-wC9SFcmh4UK0oKuLJQItoQdzS/qZ51EJegK6EmAWlh+OptpQ/npECOR3QqECd8iGHC0RJb4WKbVdSfif4ammrg==} + + '@ethersproject/abstract-signer@5.8.0': + resolution: {integrity: sha512-N0XhZTswXcmIZQdYtUnd79VJzvEwXQw6PK0dTl9VoYrEBxxCPXqS0Eod7q5TNKRxe1/5WUMuR0u0nqTF/avdCA==} + + '@ethersproject/address@5.8.0': + resolution: {integrity: sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA==} + + '@ethersproject/base64@5.8.0': + resolution: {integrity: sha512-lN0oIwfkYj9LbPx4xEkie6rAMJtySbpOAFXSDVQaBnAzYfB4X2Qr+FXJGxMoc3Bxp2Sm8OwvzMrywxyw0gLjIQ==} + + '@ethersproject/basex@5.8.0': + resolution: {integrity: sha512-PIgTszMlDRmNwW9nhS6iqtVfdTAKosA7llYXNmGPw4YAI1PUyMv28988wAb41/gHF/WqGdoLv0erHaRcHRKW2Q==} + + '@ethersproject/bignumber@5.8.0': + resolution: {integrity: sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA==} + + '@ethersproject/bytes@5.8.0': + resolution: {integrity: sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==} + + '@ethersproject/constants@5.8.0': + resolution: {integrity: sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg==} + + '@ethersproject/contracts@5.8.0': + resolution: {integrity: sha512-0eFjGz9GtuAi6MZwhb4uvUM216F38xiuR0yYCjKJpNfSEy4HUM8hvqqBj9Jmm0IUz8l0xKEhWwLIhPgxNY0yvQ==} + + '@ethersproject/hash@5.8.0': + resolution: {integrity: sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA==} + + '@ethersproject/hdnode@5.8.0': + resolution: {integrity: sha512-4bK1VF6E83/3/Im0ERnnUeWOY3P1BZml4ZD3wcH8Ys0/d1h1xaFt6Zc+Dh9zXf9TapGro0T4wvO71UTCp3/uoA==} + + '@ethersproject/json-wallets@5.8.0': + resolution: {integrity: sha512-HxblNck8FVUtNxS3VTEYJAcwiKYsBIF77W15HufqlBF9gGfhmYOJtYZp8fSDZtn9y5EaXTE87zDwzxRoTFk11w==} + + '@ethersproject/keccak256@5.8.0': + resolution: {integrity: sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng==} + + '@ethersproject/logger@5.8.0': + resolution: {integrity: sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==} + + '@ethersproject/networks@5.8.0': + resolution: {integrity: sha512-egPJh3aPVAzbHwq8DD7Po53J4OUSsA1MjQp8Vf/OZPav5rlmWUaFLiq8cvQiGK0Z5K6LYzm29+VA/p4RL1FzNg==} + + '@ethersproject/pbkdf2@5.8.0': + resolution: {integrity: sha512-wuHiv97BrzCmfEaPbUFpMjlVg/IDkZThp9Ri88BpjRleg4iePJaj2SW8AIyE8cXn5V1tuAaMj6lzvsGJkGWskg==} + + '@ethersproject/properties@5.8.0': + resolution: {integrity: sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw==} + + '@ethersproject/providers@5.8.0': + resolution: {integrity: sha512-3Il3oTzEx3o6kzcg9ZzbE+oCZYyY+3Zh83sKkn4s1DZfTUjIegHnN2Cm0kbn9YFy45FDVcuCLLONhU7ny0SsCw==} + + '@ethersproject/random@5.8.0': + resolution: {integrity: sha512-E4I5TDl7SVqyg4/kkA/qTfuLWAQGXmSOgYyO01So8hLfwgKvYK5snIlzxJMk72IFdG/7oh8yuSqY2KX7MMwg+A==} + + '@ethersproject/rlp@5.8.0': + resolution: {integrity: sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q==} + + '@ethersproject/sha2@5.8.0': + resolution: {integrity: sha512-dDOUrXr9wF/YFltgTBYS0tKslPEKr6AekjqDW2dbn1L1xmjGR+9GiKu4ajxovnrDbwxAKdHjW8jNcwfz8PAz4A==} + + '@ethersproject/signing-key@5.8.0': + resolution: {integrity: sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w==} + + '@ethersproject/solidity@5.8.0': + resolution: {integrity: sha512-4CxFeCgmIWamOHwYN9d+QWGxye9qQLilpgTU0XhYs1OahkclF+ewO+3V1U0mvpiuQxm5EHHmv8f7ClVII8EHsA==} + + '@ethersproject/strings@5.8.0': + resolution: {integrity: sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg==} + + '@ethersproject/transactions@5.8.0': + resolution: {integrity: sha512-UglxSDjByHG0TuU17bDfCemZ3AnKO2vYrL5/2n2oXvKzvb7Cz+W9gOWXKARjp2URVwcWlQlPOEQyAviKwT4AHg==} + + '@ethersproject/units@5.8.0': + resolution: {integrity: sha512-lxq0CAnc5kMGIiWW4Mr041VT8IhNM+Pn5T3haO74XZWFulk7wH1Gv64HqE96hT4a7iiNMdOCFEBgaxWuk8ETKQ==} + + '@ethersproject/wallet@5.8.0': + resolution: {integrity: sha512-G+jnzmgg6UxurVKRKvw27h0kvG75YKXZKdlLYmAHeF32TGUzHkOFd7Zn6QHOTYRFWnfjtSSFjBowKo7vfrXzPA==} + + '@ethersproject/web@5.8.0': + resolution: {integrity: sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw==} + + '@ethersproject/wordlists@5.8.0': + resolution: {integrity: sha512-2df9bbXicZws2Sb5S6ET493uJ0Z84Fjr3pC4tu/qlnZERibZCeUVuqdtt+7Tv9xxhUxHoIekIA7avrKUWHrezg==} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@napi-rs/wasm-runtime@1.2.0': + resolution: {integrity: sha512-kDoONqMa+VnZ4vvvu/ZUurpJ4gkZU57e7g69qpNgWhYcZFPUHZM2CEMKm+cG6ufDVALbjMvfmMjFVqaK7uEMnA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} + peerDependencies: + '@emnapi/core': ^2.0.0-alpha.3 + '@emnapi/runtime': ^2.0.0-alpha.3 + + '@opentelemetry/api@1.9.1': + resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} + engines: {node: '>=8.0.0'} + + '@oxc-project/types@0.139.0': + resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==} + + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + + '@rolldown/binding-android-arm64@1.1.5': + resolution: {integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.1.5': + resolution: {integrity: sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.1.5': + resolution: {integrity: sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.1.5': + resolution: {integrity: sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.1.5': + resolution: {integrity: sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.1.5': + resolution: {integrity: sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-arm64-musl@1.1.5': + resolution: {integrity: sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-ppc64-gnu@1.1.5': + resolution: {integrity: sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@rolldown/binding-linux-s390x-gnu@1.1.5': + resolution: {integrity: sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@rolldown/binding-linux-x64-gnu@1.1.5': + resolution: {integrity: sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-linux-x64-musl@1.1.5': + resolution: {integrity: sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-openharmony-arm64@1.1.5': + resolution: {integrity: sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.1.5': + resolution: {integrity: sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.1.5': + resolution: {integrity: sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.1.5': + resolution: {integrity: sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/node@25.9.5': + resolution: {integrity: sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg==} + + '@types/pg@8.20.0': + resolution: {integrity: sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==} + + '@vitest/expect@4.1.10': + resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} + + '@vitest/mocker@4.1.10': + resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.10': + resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} + + '@vitest/runner@4.1.10': + resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} + + '@vitest/snapshot@4.1.10': + resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} + + '@vitest/spy@4.1.10': + resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} + + '@vitest/utils@4.1.10': + resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} + + aes-js@3.0.0: + resolution: {integrity: sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + + bech32@1.1.4: + resolution: {integrity: sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==} + + bintrees@1.0.2: + resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==} + + bn.js@4.12.5: + resolution: {integrity: sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==} + + bn.js@5.2.5: + resolution: {integrity: sha512-Vq886eXykuP5E6HcKSSStP3bJgrE6In5WKxVUvJ8XGpWWYs2xZHWqUwzCtGgEtBcxyd57KBFDPFoUfNzdaHCNg==} + + brorand@1.1.0: + resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + + dateformat@4.6.3: + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + discontinuous-range@1.0.0: + resolution: {integrity: sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==} + + dotenv@17.4.2: + resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + elliptic@6.6.1: + resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@2.3.1: + resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + engines: {node: '>=18'} + hasBin: true + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + ethers@5.8.0: + resolution: {integrity: sha512-DUq+7fHrCg1aPDFCHx6UIPb3nmt2XMpM7Y/g2gLhsl3lIBqeAfOJIl1qEvRf2uq3BiKxmh6Fh5pfp2ieyek7Kg==} + + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + fast-copy@4.0.3: + resolution: {integrity: sha512-58apWr0GUiDFM8+3afrO6eYwJBn9ZAhDOzG3L+/9llab/haCARS2UIfffmOurYLwbgDRs8n0rfr6qAAPEAuAQw==} + + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + functional-red-black-tree@1.0.1: + resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hash.js@1.1.7: + resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + help-me@5.0.0: + resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} + + hmac-drbg@1.0.1: + resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} + + immutable@4.3.9: + resolution: {integrity: sha512-ObHy4YN7ycwZOUCLI1/6svfyAFu7vL8RhAvVu/bh/RZW9EPlOyDaQ9jDQWCtdqzaXUjgXZCW1migtHE7YI7UGQ==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + js-sha3@0.8.0: + resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} + + json-stable-stringify@1.3.0: + resolution: {integrity: sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==} + engines: {node: '>= 0.4'} + + jsonify@0.0.1: + resolution: {integrity: sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==} + + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + + minimalistic-crypto-utils@1.0.1: + resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + + moo@0.5.3: + resolution: {integrity: sha512-m2fmM2dDm7GZQsY7KK2cme8agi+AAljILjQnof7p1ZMDe6dQ4bdnSMx0cPppudoeNv5hEFQirN6u+O4fDE0IWA==} + + nanoid@3.3.16: + resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + nearley@2.20.1: + resolution: {integrity: sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==} + hasBin: true + + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + object-hash@2.2.0: + resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==} + engines: {node: '>= 6'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} + engines: {node: '>=12.20.0'} + + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pg-cloudflare@1.4.0: + resolution: {integrity: sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==} + + pg-connection-string@2.12.0: + resolution: {integrity: sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-mem@3.0.14: + resolution: {integrity: sha512-G9m8OD0A+YS083smidSUJddTX2dEDPT8mRMG3sQGNiGfS/mkvAgd9Kf1/onD5633bFN7HcQK/Tn2x7qjBMFRUQ==} + peerDependencies: + '@mikro-orm/core': '>=4.5.3' + '@mikro-orm/postgresql': '>=4.5.3' + knex: '>=0.20' + kysely: '>=0.26' + mikro-orm: '*' + pg-promise: '>=10.8.7' + pg-server: ^0.1.5 + postgres: ^3.4.4 + slonik: '>=23.0.1' + typeorm: '>=0.2.29' + peerDependenciesMeta: + '@mikro-orm/core': + optional: true + '@mikro-orm/postgresql': + optional: true + knex: + optional: true + kysely: + optional: true + mikro-orm: + optional: true + pg-promise: + optional: true + pg-server: + optional: true + postgres: + optional: true + slonik: + optional: true + typeorm: + optional: true + + pg-pool@3.13.0: + resolution: {integrity: sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==} + peerDependencies: + pg: '>=8.0' + + pg-protocol@1.13.0: + resolution: {integrity: sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + pg@8.20.0: + resolution: {integrity: sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==} + engines: {node: '>= 16.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + + pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + + pgsql-ast-parser@12.0.2: + resolution: {integrity: sha512-1WWa96Sw6h4uv9GLw98EzH/+xoBTC8j2TwV/AMW3E+Ir/fHOu/jLLbj6kPiz3y2bGISTKNYvKWwHoqvQ5FLuAw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} + + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} + + pino-pretty@13.1.3: + resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==} + hasBin: true + + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@10.3.1: + resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==} + hasBin: true + + postcss@8.5.23: + resolution: {integrity: sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==} + engines: {node: ^10 || ^12 || >=14} + + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-bytea@1.0.1: + resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} + engines: {node: '>=0.10.0'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + + prom-client@15.1.3: + resolution: {integrity: sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==} + engines: {node: ^16 || ^18 || >=20} + + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + + railroad-diagrams@1.0.0: + resolution: {integrity: sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==} + + randexp@0.4.6: + resolution: {integrity: sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==} + engines: {node: '>=0.12'} + + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + + real-require@1.0.0: + resolution: {integrity: sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==} + + ret@0.1.15: + resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==} + engines: {node: '>=0.12'} + + rolldown@1.1.5: + resolution: {integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + scrypt-js@3.0.1: + resolution: {integrity: sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==} + + secure-json-parse@4.1.0: + resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + sonic-boom@4.2.1: + resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@4.2.0: + resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} + + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + + tdigest@0.1.2: + resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==} + + thread-stream@4.2.0: + resolution: {integrity: sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==} + engines: {node: '>=20'} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.2.4: + resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.23.1: + resolution: {integrity: sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==} + engines: {node: '>=18.0.0'} + hasBin: true + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + + vite@8.1.5: + resolution: {integrity: sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.3.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.10: + resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.10 + '@vitest/browser-preview': 4.1.10 + '@vitest/browser-webdriverio': 4.1.10 + '@vitest/coverage-istanbul': 4.1.10 + '@vitest/coverage-v8': 4.1.10 + '@vitest/ui': 4.1.10 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + +snapshots: + + '@emnapi/core@1.11.1': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.28.1': + optional: true + + '@esbuild/android-arm64@0.28.1': + optional: true + + '@esbuild/android-arm@0.28.1': + optional: true + + '@esbuild/android-x64@0.28.1': + optional: true + + '@esbuild/darwin-arm64@0.28.1': + optional: true + + '@esbuild/darwin-x64@0.28.1': + optional: true + + '@esbuild/freebsd-arm64@0.28.1': + optional: true + + '@esbuild/freebsd-x64@0.28.1': + optional: true + + '@esbuild/linux-arm64@0.28.1': + optional: true + + '@esbuild/linux-arm@0.28.1': + optional: true + + '@esbuild/linux-ia32@0.28.1': + optional: true + + '@esbuild/linux-loong64@0.28.1': + optional: true + + '@esbuild/linux-mips64el@0.28.1': + optional: true + + '@esbuild/linux-ppc64@0.28.1': + optional: true + + '@esbuild/linux-riscv64@0.28.1': + optional: true + + '@esbuild/linux-s390x@0.28.1': + optional: true + + '@esbuild/linux-x64@0.28.1': + optional: true + + '@esbuild/netbsd-arm64@0.28.1': + optional: true + + '@esbuild/netbsd-x64@0.28.1': + optional: true + + '@esbuild/openbsd-arm64@0.28.1': + optional: true + + '@esbuild/openbsd-x64@0.28.1': + optional: true + + '@esbuild/openharmony-arm64@0.28.1': + optional: true + + '@esbuild/sunos-x64@0.28.1': + optional: true + + '@esbuild/win32-arm64@0.28.1': + optional: true + + '@esbuild/win32-ia32@0.28.1': + optional: true + + '@esbuild/win32-x64@0.28.1': + optional: true + + '@ethersproject/abi@5.8.0': + dependencies: + '@ethersproject/address': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/hash': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/strings': 5.8.0 + + '@ethersproject/abstract-provider@5.8.0': + dependencies: + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/networks': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/transactions': 5.8.0 + '@ethersproject/web': 5.8.0 + + '@ethersproject/abstract-signer@5.8.0': + dependencies: + '@ethersproject/abstract-provider': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + + '@ethersproject/address@5.8.0': + dependencies: + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/rlp': 5.8.0 + + '@ethersproject/base64@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + + '@ethersproject/basex@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/properties': 5.8.0 + + '@ethersproject/bignumber@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + bn.js: 5.2.5 + + '@ethersproject/bytes@5.8.0': + dependencies: + '@ethersproject/logger': 5.8.0 + + '@ethersproject/constants@5.8.0': + dependencies: + '@ethersproject/bignumber': 5.8.0 + + '@ethersproject/contracts@5.8.0': + dependencies: + '@ethersproject/abi': 5.8.0 + '@ethersproject/abstract-provider': 5.8.0 + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/address': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/transactions': 5.8.0 + + '@ethersproject/hash@5.8.0': + dependencies: + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/address': 5.8.0 + '@ethersproject/base64': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/strings': 5.8.0 + + '@ethersproject/hdnode@5.8.0': + dependencies: + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/basex': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/pbkdf2': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/sha2': 5.8.0 + '@ethersproject/signing-key': 5.8.0 + '@ethersproject/strings': 5.8.0 + '@ethersproject/transactions': 5.8.0 + '@ethersproject/wordlists': 5.8.0 + + '@ethersproject/json-wallets@5.8.0': + dependencies: + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/address': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/hdnode': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/pbkdf2': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/random': 5.8.0 + '@ethersproject/strings': 5.8.0 + '@ethersproject/transactions': 5.8.0 + aes-js: 3.0.0 + scrypt-js: 3.0.1 + + '@ethersproject/keccak256@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + js-sha3: 0.8.0 + + '@ethersproject/logger@5.8.0': {} + + '@ethersproject/networks@5.8.0': + dependencies: + '@ethersproject/logger': 5.8.0 + + '@ethersproject/pbkdf2@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/sha2': 5.8.0 + + '@ethersproject/properties@5.8.0': + dependencies: + '@ethersproject/logger': 5.8.0 + + '@ethersproject/providers@5.8.0': + dependencies: + '@ethersproject/abstract-provider': 5.8.0 + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/address': 5.8.0 + '@ethersproject/base64': 5.8.0 + '@ethersproject/basex': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/hash': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/networks': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/random': 5.8.0 + '@ethersproject/rlp': 5.8.0 + '@ethersproject/sha2': 5.8.0 + '@ethersproject/strings': 5.8.0 + '@ethersproject/transactions': 5.8.0 + '@ethersproject/web': 5.8.0 + bech32: 1.1.4 + ws: 8.18.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@ethersproject/random@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + + '@ethersproject/rlp@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + + '@ethersproject/sha2@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + hash.js: 1.1.7 + + '@ethersproject/signing-key@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + bn.js: 5.2.5 + elliptic: 6.6.1 + hash.js: 1.1.7 + + '@ethersproject/solidity@5.8.0': + dependencies: + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/sha2': 5.8.0 + '@ethersproject/strings': 5.8.0 + + '@ethersproject/strings@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/logger': 5.8.0 + + '@ethersproject/transactions@5.8.0': + dependencies: + '@ethersproject/address': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/rlp': 5.8.0 + '@ethersproject/signing-key': 5.8.0 + + '@ethersproject/units@5.8.0': + dependencies: + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/logger': 5.8.0 + + '@ethersproject/wallet@5.8.0': + dependencies: + '@ethersproject/abstract-provider': 5.8.0 + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/address': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/hash': 5.8.0 + '@ethersproject/hdnode': 5.8.0 + '@ethersproject/json-wallets': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/random': 5.8.0 + '@ethersproject/signing-key': 5.8.0 + '@ethersproject/transactions': 5.8.0 + '@ethersproject/wordlists': 5.8.0 + + '@ethersproject/web@5.8.0': + dependencies: + '@ethersproject/base64': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/strings': 5.8.0 + + '@ethersproject/wordlists@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/hash': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/strings': 5.8.0 + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@napi-rs/wasm-runtime@1.2.0(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@opentelemetry/api@1.9.1': {} + + '@oxc-project/types@0.139.0': {} + + '@pinojs/redact@0.4.0': {} + + '@rolldown/binding-android-arm64@1.1.5': + optional: true + + '@rolldown/binding-darwin-arm64@1.1.5': + optional: true + + '@rolldown/binding-darwin-x64@1.1.5': + optional: true + + '@rolldown/binding-freebsd-x64@1.1.5': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.1.5': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.1.5': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-x64-musl@1.1.5': + optional: true + + '@rolldown/binding-openharmony-arm64@1.1.5': + optional: true + + '@rolldown/binding-wasm32-wasi@1.1.5': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@napi-rs/wasm-runtime': 1.2.0(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.1.5': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.1.5': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@standard-schema/spec@1.1.0': {} + + '@tybys/wasm-util@0.10.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.9': {} + + '@types/node@25.9.5': + dependencies: + undici-types: 7.24.6 + + '@types/pg@8.20.0': + dependencies: + '@types/node': 25.9.5 + pg-protocol: 1.13.0 + pg-types: 2.2.0 + + '@vitest/expect@4.1.10': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.10(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(tsx@4.23.1))': + dependencies: + '@vitest/spy': 4.1.10 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(tsx@4.23.1) + + '@vitest/pretty-format@4.1.10': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.10': + dependencies: + '@vitest/utils': 4.1.10 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.10': + dependencies: + '@vitest/pretty-format': 4.1.10 + '@vitest/utils': 4.1.10 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.10': {} + + '@vitest/utils@4.1.10': + dependencies: + '@vitest/pretty-format': 4.1.10 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + + aes-js@3.0.0: {} + + assertion-error@2.0.1: {} + + atomic-sleep@1.0.0: {} + + bech32@1.1.4: {} + + bintrees@1.0.2: {} + + bn.js@4.12.5: {} + + bn.js@5.2.5: {} + + brorand@1.1.0: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.9: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + chai@6.2.2: {} + + colorette@2.0.20: {} + + commander@2.20.3: {} + + convert-source-map@2.0.0: {} + + data-uri-to-buffer@4.0.1: {} + + dateformat@4.6.3: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + detect-libc@2.1.2: {} + + discontinuous-range@1.0.0: {} + + dotenv@17.4.2: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + elliptic@6.6.1: + dependencies: + bn.js: 4.12.5 + brorand: 1.1.0 + hash.js: 1.1.7 + hmac-drbg: 1.0.1 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@2.3.1: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + esbuild@0.28.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + ethers@5.8.0: + dependencies: + '@ethersproject/abi': 5.8.0 + '@ethersproject/abstract-provider': 5.8.0 + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/address': 5.8.0 + '@ethersproject/base64': 5.8.0 + '@ethersproject/basex': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/contracts': 5.8.0 + '@ethersproject/hash': 5.8.0 + '@ethersproject/hdnode': 5.8.0 + '@ethersproject/json-wallets': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/networks': 5.8.0 + '@ethersproject/pbkdf2': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/providers': 5.8.0 + '@ethersproject/random': 5.8.0 + '@ethersproject/rlp': 5.8.0 + '@ethersproject/sha2': 5.8.0 + '@ethersproject/signing-key': 5.8.0 + '@ethersproject/solidity': 5.8.0 + '@ethersproject/strings': 5.8.0 + '@ethersproject/transactions': 5.8.0 + '@ethersproject/units': 5.8.0 + '@ethersproject/wallet': 5.8.0 + '@ethersproject/web': 5.8.0 + '@ethersproject/wordlists': 5.8.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + expect-type@1.4.0: {} + + fast-copy@4.0.3: {} + + fast-safe-stringify@2.1.1: {} + + fdir@6.5.0(picomatch@4.0.5): + optionalDependencies: + picomatch: 4.0.5 + + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + functional-red-black-tree@1.0.1: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + gopd@1.2.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-symbols@1.1.0: {} + + hash.js@1.1.7: + dependencies: + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + help-me@5.0.0: {} + + hmac-drbg@1.0.1: + dependencies: + hash.js: 1.1.7 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 + + immutable@4.3.9: {} + + inherits@2.0.4: {} + + isarray@2.0.5: {} + + joycon@3.1.1: {} + + js-sha3@0.8.0: {} + + json-stable-stringify@1.3.0: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + isarray: 2.0.5 + jsonify: 0.0.1 + object-keys: 1.1.1 + + jsonify@0.0.1: {} + + lightningcss-android-arm64@1.33.0: + optional: true + + lightningcss-darwin-arm64@1.33.0: + optional: true + + lightningcss-darwin-x64@1.33.0: + optional: true + + lightningcss-freebsd-x64@1.33.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + + lightningcss-linux-arm64-musl@1.33.0: + optional: true + + lightningcss-linux-x64-gnu@1.33.0: + optional: true + + lightningcss-linux-x64-musl@1.33.0: + optional: true + + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + + lightningcss-win32-x64-msvc@1.33.0: + optional: true + + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + minimalistic-assert@1.0.1: {} + + minimalistic-crypto-utils@1.0.1: {} + + minimist@1.2.8: {} + + moment@2.30.1: {} + + moo@0.5.3: {} + + nanoid@3.3.16: {} + + nearley@2.20.1: + dependencies: + commander: 2.20.3 + moo: 0.5.3 + railroad-diagrams: 1.0.0 + randexp: 0.4.6 + + node-domexception@1.0.0: {} + + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + + object-hash@2.2.0: {} + + object-keys@1.1.1: {} + + obug@2.1.4: {} + + on-exit-leak-free@2.1.2: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + pathe@2.0.3: {} + + pg-cloudflare@1.4.0: + optional: true + + pg-connection-string@2.12.0: {} + + pg-int8@1.0.1: {} + + pg-mem@3.0.14: + dependencies: + functional-red-black-tree: 1.0.1 + immutable: 4.3.9 + json-stable-stringify: 1.3.0 + lru-cache: 6.0.0 + moment: 2.30.1 + object-hash: 2.2.0 + pgsql-ast-parser: 12.0.2 + + pg-pool@3.13.0(pg@8.20.0): + dependencies: + pg: 8.20.0 + + pg-protocol@1.13.0: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.1 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + pg@8.20.0: + dependencies: + pg-connection-string: 2.12.0 + pg-pool: 3.13.0(pg@8.20.0) + pg-protocol: 1.13.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.4.0 + + pgpass@1.0.5: + dependencies: + split2: 4.2.0 + + pgsql-ast-parser@12.0.2: + dependencies: + moo: 0.5.3 + nearley: 2.20.1 + + picocolors@1.1.1: {} + + picomatch@4.0.5: {} + + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 + + pino-pretty@13.1.3: + dependencies: + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 4.0.3 + fast-safe-stringify: 2.1.1 + help-me: 5.0.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pump: 3.0.4 + secure-json-parse: 4.1.0 + sonic-boom: 4.2.1 + strip-json-comments: 5.0.3 + + pino-std-serializers@7.1.0: {} + + pino@10.3.1: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.1 + thread-stream: 4.2.0 + + postcss@8.5.23: + dependencies: + nanoid: 3.3.16 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postgres-array@2.0.0: {} + + postgres-bytea@1.0.1: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + + process-warning@5.0.0: {} + + prom-client@15.1.3: + dependencies: + '@opentelemetry/api': 1.9.1 + tdigest: 0.1.2 + + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + quick-format-unescaped@4.0.4: {} + + railroad-diagrams@1.0.0: {} + + randexp@0.4.6: + dependencies: + discontinuous-range: 1.0.0 + ret: 0.1.15 + + real-require@0.2.0: {} + + real-require@1.0.0: {} + + ret@0.1.15: {} + + rolldown@1.1.5: + dependencies: + '@oxc-project/types': 0.139.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.1.5 + '@rolldown/binding-darwin-arm64': 1.1.5 + '@rolldown/binding-darwin-x64': 1.1.5 + '@rolldown/binding-freebsd-x64': 1.1.5 + '@rolldown/binding-linux-arm-gnueabihf': 1.1.5 + '@rolldown/binding-linux-arm64-gnu': 1.1.5 + '@rolldown/binding-linux-arm64-musl': 1.1.5 + '@rolldown/binding-linux-ppc64-gnu': 1.1.5 + '@rolldown/binding-linux-s390x-gnu': 1.1.5 + '@rolldown/binding-linux-x64-gnu': 1.1.5 + '@rolldown/binding-linux-x64-musl': 1.1.5 + '@rolldown/binding-openharmony-arm64': 1.1.5 + '@rolldown/binding-wasm32-wasi': 1.1.5 + '@rolldown/binding-win32-arm64-msvc': 1.1.5 + '@rolldown/binding-win32-x64-msvc': 1.1.5 + + safe-stable-stringify@2.5.0: {} + + scrypt-js@3.0.1: {} + + secure-json-parse@4.1.0: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + siginfo@2.0.0: {} + + sonic-boom@4.2.1: + dependencies: + atomic-sleep: 1.0.0 + + source-map-js@1.2.1: {} + + split2@4.2.0: {} + + stackback@0.0.2: {} + + std-env@4.2.0: {} + + strip-json-comments@5.0.3: {} + + tdigest@0.1.2: + dependencies: + bintrees: 1.0.2 + + thread-stream@4.2.0: + dependencies: + real-require: 1.0.0 + + tinybench@2.9.0: {} + + tinyexec@1.2.4: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + + tinyrainbow@3.1.0: {} + + tslib@2.8.1: + optional: true + + tsx@4.23.1: + dependencies: + esbuild: 0.28.1 + optionalDependencies: + fsevents: 2.3.3 + + typescript@5.9.3: {} + + undici-types@7.24.6: {} + + vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(tsx@4.23.1): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.5 + postcss: 8.5.23 + rolldown: 1.1.5 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 25.9.5 + esbuild: 0.28.1 + fsevents: 2.3.3 + tsx: 4.23.1 + + vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.9.5)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(tsx@4.23.1)): + dependencies: + '@vitest/expect': 4.1.10 + '@vitest/mocker': 4.1.10(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(tsx@4.23.1)) + '@vitest/pretty-format': 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + es-module-lexer: 2.3.1 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.1.4 + pathe: 2.0.3 + picomatch: 4.0.5 + std-env: 4.2.0 + tinybench: 2.9.0 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.0 + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(tsx@4.23.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@opentelemetry/api': 1.9.1 + '@types/node': 25.9.5 + transitivePeerDependencies: + - msw + + web-streams-polyfill@3.3.3: {} + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + wrappy@1.0.2: {} + + ws@8.18.0: {} + + xtend@4.0.2: {} + + yallist@4.0.0: {} + + zod@4.4.3: {} diff --git a/indexer/src/chain/events.ts b/indexer/src/chain/events.ts new file mode 100644 index 0000000..e5fc418 --- /dev/null +++ b/indexer/src/chain/events.ts @@ -0,0 +1,291 @@ +import { ethers } from 'ethers' + +/** Events the indexer decodes. Must match ComplianceDVN.sol and ERC-20. */ +export const DVN_ABI = [ + 'event RiskVerdict(bytes32 indexed payloadHash, uint8 action, uint16 score, uint256 reasonMask, bytes32 evidenceHash)', + 'event PacketApproved(bytes32 indexed payloadHash, address approver)', +] +export const ERC20_ABI = ['event Transfer(address indexed from, address indexed to, uint256 value)'] + +/** The OFT's own account of a cross-chain send: who sent it, how much, and to which chain. */ +export const OFT_ABI = [ + 'event OFTSent(bytes32 indexed guid, uint32 dstEid, address indexed fromAddress, uint256 amountSentLD, uint256 amountReceivedLD)', +] +/** The endpoint carries the packet, and the packet is the only place the recipient is named. */ +export const ENDPOINT_ABI = ['event PacketSent(bytes encodedPayload, bytes options, address sendLibrary)'] + +export const dvnInterface = new ethers.utils.Interface(DVN_ABI) +export const erc20Interface = new ethers.utils.Interface(ERC20_ABI) +export const oftInterface = new ethers.utils.Interface(OFT_ABI) +export const endpointInterface = new ethers.utils.Interface(ENDPOINT_ABI) + +export interface LogRef { + blockNumber: number + txHash: string + logIndex: number +} + +export interface RiskVerdictRow extends LogRef { + payloadHash: string + action: number + score: number + /** Decimal string — a uint256 mask does not fit a JS number. */ + reasonMask: string + evidenceHash: string +} + +export interface PacketApprovalRow extends LogRef { + payloadHash: string + approver: string +} + +export interface TransferRow extends LogRef { + token: string + from: string + to: string + /** Decimal string — uint256. */ + value: string +} + +/** + * One cross-chain send, reassembled from the two halves the chain records separately. + * + * `to` is an address on another chain — the only edge in the graph where the two ends do not share + * one. `dstEid` says which, so a path is never continued on the wrong side of the bridge. + */ +export interface BridgeSendRow extends LogRef { + token: string + from: string + to: string + dstEid: number + /** Decimal string — amountSentLD, the amount debited on this side. */ + value: string + guid: string +} + +/** Block identity plus its wall-clock time — the schema's only timestamp source. */ +export interface BlockRef { + hash: string + parentHash: string + timestamp: number +} + +/** The slice of an ethers provider the scanner needs, kept tiny for offline tests. */ +export interface LogSource { + getBlockNumber(): Promise + getLogs(filter: { + address?: string + topics?: (string | null)[] + fromBlock: number + toBlock: number + }): Promise + getBlock(blockNumber: number): Promise +} + +const topic = (iface: ethers.utils.Interface, name: string) => iface.getEventTopic(name) + +export function decodeRiskVerdict(log: ethers.providers.Log): RiskVerdictRow { + const d = dvnInterface.decodeEventLog('RiskVerdict', log.data, log.topics) + return { + blockNumber: log.blockNumber, + txHash: log.transactionHash.toLowerCase(), + logIndex: log.logIndex, + payloadHash: (d.payloadHash as string).toLowerCase(), + action: Number(d.action), + score: Number(d.score), + reasonMask: (d.reasonMask as ethers.BigNumber).toString(), + evidenceHash: (d.evidenceHash as string).toLowerCase(), + } +} + +export function decodePacketApproved(log: ethers.providers.Log): PacketApprovalRow { + const d = dvnInterface.decodeEventLog('PacketApproved', log.data, log.topics) + return { + blockNumber: log.blockNumber, + txHash: log.transactionHash.toLowerCase(), + logIndex: log.logIndex, + payloadHash: (d.payloadHash as string).toLowerCase(), + approver: (d.approver as string).toLowerCase(), + } +} + +export function decodeTransfer(log: ethers.providers.Log): TransferRow { + const d = erc20Interface.decodeEventLog('Transfer', log.data, log.topics) + return { + blockNumber: log.blockNumber, + txHash: log.transactionHash.toLowerCase(), + logIndex: log.logIndex, + token: log.address.toLowerCase(), + from: (d.from as string).toLowerCase(), + to: (d.to as string).toLowerCase(), + value: (d.value as ethers.BigNumber).toString(), + } +} + +/** Scan our DVN for both of its risk events in one pass. */ +export async function scanDvnEvents( + source: LogSource, + dvnAddress: string, + fromBlock: number, + toBlock: number, +): Promise<{ verdicts: RiskVerdictRow[]; approvals: PacketApprovalRow[] }> { + const verdictTopic = topic(dvnInterface, 'RiskVerdict') + const approvedTopic = topic(dvnInterface, 'PacketApproved') + // One request with a topic0 OR-set rather than two round trips. + const logs = await source.getLogs({ + address: dvnAddress, + topics: [[verdictTopic, approvedTopic] as unknown as string], + fromBlock, + toBlock, + }) + + const verdicts: RiskVerdictRow[] = [] + const approvals: PacketApprovalRow[] = [] + for (const log of logs) { + if (log.topics[0] === verdictTopic) verdicts.push(decodeRiskVerdict(log)) + else if (log.topics[0] === approvedTopic) approvals.push(decodePacketApproved(log)) + } + return { verdicts, approvals } +} + +/** + * LayerZero V2 packet layout: `header(81) ‖ guid(32) ‖ message`. + * + * The guid sits in the payload, so pairing a packet with its `OFTSent` needs no derivation — and + * the OFT message opens with the recipient as a 32-byte word, which is the fact the source chain + * records nowhere else. + */ +export function decodePacketRecipient(encodedPayload: string): { guid: string; to: string } | undefined { + const hex = encodedPayload.startsWith('0x') ? encodedPayload.slice(2) : encodedPayload + // 113 bytes of framing, then at least the 32-byte recipient word. + if (hex.length < (113 + 32) * 2) return undefined + return { + guid: '0x' + hex.slice(81 * 2, 113 * 2), + // sendTo is a bytes32; an EVM address is its low 20 bytes. + to: '0x' + hex.slice(113 * 2 + 24, 113 * 2 + 64), + } +} + +/** + * Reconstruct this range's cross-chain sends. + * + * Neither event alone is an edge: `OFTSent` knows the sender but not the recipient, and the packet + * knows the recipient but names the OFT contract as its sender. Joined on the guid they are one + * transfer between two parties, which is what the graph needs — and both are emitted on the source + * chain, so the edge exists even for a send that never arrived because we blocked it. + * + * A send whose packet is missing from the range is skipped rather than guessed at. It happens at a + * chunk boundary only if the two logs straddle it, which they cannot: they are in one transaction. + */ +export async function scanBridgeSends( + source: LogSource, + tokens: readonly string[], + endpoint: string, + fromBlock: number, + toBlock: number, + onSkip?: (guid: string, reason: string) => void, +): Promise { + if (tokens.length === 0) return [] + const sentTopic = topic(oftInterface, 'OFTSent') + + const sends: Array<{ log: ethers.providers.Log; guid: string; from: string; dstEid: number; value: string }> = [] + for (const token of tokens) { + const logs = await source.getLogs({ address: token, topics: [sentTopic], fromBlock, toBlock }) + for (const log of logs) { + try { + const d = oftInterface.decodeEventLog('OFTSent', log.data, log.topics) + sends.push({ + log, + guid: (d.guid as string).toLowerCase(), + from: (d.fromAddress as string).toLowerCase(), + dstEid: Number(d.dstEid), + value: (d.amountSentLD as ethers.BigNumber).toString(), + }) + } catch (err) { + onSkip?.(log.transactionHash, (err as Error).message) + } + } + } + if (sends.length === 0) return [] + + // One request for the whole range, and only when the range holds one of our sends. `PacketSent` + // indexes none of its parameters, so it cannot be filtered to our OApps — this pulls every + // packet in the chunk. Fine at testnet volumes (single digits per chunk here); a busy chain + // would want to read the receipts of our own send transactions instead. + // + // The guid, not the transaction, is what identifies ours, so a batched send resolves correctly. + const packets = await source.getLogs({ + address: endpoint, + topics: [topic(endpointInterface, 'PacketSent')], + fromBlock, + toBlock, + }) + const recipients = new Map() + for (const log of packets) { + try { + const { encodedPayload } = endpointInterface.decodeEventLog('PacketSent', log.data, log.topics) + const decoded = decodePacketRecipient(encodedPayload as string) + if (decoded) recipients.set(decoded.guid.toLowerCase(), decoded.to.toLowerCase()) + } catch { + // Another OApp's packet shape we cannot read is not ours to care about. + } + } + + const out: BridgeSendRow[] = [] + for (const s of sends) { + const to = recipients.get(s.guid) + if (!to) { + onSkip?.(s.guid, 'no PacketSent found for this guid') + continue + } + out.push({ + blockNumber: s.log.blockNumber, + txHash: s.log.transactionHash.toLowerCase(), + logIndex: s.log.logIndex, + token: s.log.address.toLowerCase(), + from: s.from, + to, + dstEid: s.dstEid, + value: s.value, + guid: s.guid, + }) + } + return out +} + +/** + * Scan `Transfer` events for the tracked tokens. + * + * Only ERC-20 transfers are collected. Native-value transfers would require trace APIs, which + * most public RPCs do not expose, so the graph is token-transfer-shaped for now — worth knowing + * when reading an exposure result. + * + * A log that will not decode is skipped rather than thrown: ERC-721 shares the Transfer topic + * but keeps all three parameters indexed, so a non-ERC-20 contract in TRACKED_TOKENS would + * otherwise abort the same chunk every tick and freeze the cursor for good. `onSkip` keeps the + * skips visible instead of silent. + */ +export async function scanTransfers( + source: LogSource, + tokens: readonly string[], + fromBlock: number, + toBlock: number, + onSkip?: (token: string, reason: string) => void, +): Promise { + if (tokens.length === 0) return [] + const transferTopic = topic(erc20Interface, 'Transfer') + const out: TransferRow[] = [] + // getLogs takes a single address or an array depending on the node; querying per token keeps + // behaviour identical across providers and bounds each response. + for (const token of tokens) { + const logs = await source.getLogs({ address: token, topics: [transferTopic], fromBlock, toBlock }) + for (const log of logs) { + try { + out.push(decodeTransfer(log)) + } catch (err) { + onSkip?.(log.address.toLowerCase(), (err as Error).message) + } + } + } + return out +} diff --git a/indexer/src/config.ts b/indexer/src/config.ts new file mode 100644 index 0000000..6e2db5f --- /dev/null +++ b/indexer/src/config.ts @@ -0,0 +1,317 @@ +import { z } from 'zod' + +import { DEFAULT_SOURCIFY_URL } from './verify/sourcify' + +/** + * Static chain metadata. Mirrors the worker's CHAIN_REGISTRY — the indexer watches the same + * DVN contracts, so the two must agree on EIDs and endpoints. + */ +export interface ChainStatic { + name: string + eid: number + /** EVM chain id — what the source verifier keys on, as opposed to the LayerZero eid. */ + chainId: number + rpcEnv: string + rpcDefault: string + dvnEnv: string +} + +export const CHAIN_REGISTRY: Record = { + baseSepolia: { + name: 'base-sepolia', + eid: 40245, + chainId: 84532, + rpcEnv: 'RPC_URL_BASE_SEPOLIA', + rpcDefault: 'https://sepolia.base.org', + dvnEnv: 'DVN_BASE_SEPOLIA', + }, + optimismSepolia: { + name: 'optimism-sepolia', + eid: 40232, + chainId: 11155420, + rpcEnv: 'RPC_URL_OPTIMISM_SEPOLIA', + rpcDefault: 'https://sepolia.optimism.io', + dvnEnv: 'DVN_OPTIMISM_SEPOLIA', + }, +} + +/** + * A 32-byte private key, with the 0x prefix optional. + * + * ethers accepts a bare 64-hex key, so requiring the prefix would reject a configuration that + * works perfectly well. Values are normalized to the 0x form below so everything downstream sees + * one shape. + */ +const HEX_PRIVATE_KEY = /^(0x)?[0-9a-fA-F]{64}$/ + +/** Canonicalize to the 0x form. */ +const withHexPrefix = (v: string) => (v.startsWith('0x') ? v : `0x${v}`) +const EVM_ADDRESS = /^0x[0-9a-fA-F]{40}$/ +const LOG_LEVELS = ['fatal', 'error', 'warn', 'info', 'debug', 'trace', 'silent'] as const + +export interface ResolvedChain { + key: string + name: string + eid: number + chainId: number + rpc: string + dvn: string + /** EndpointV2 — read for `PacketSent`, which is where a cross-chain send names its recipient. */ + endpoint: string +} + +/** + * EndpointV2 is deployed at one address across every LayerZero V2 chain, so it is a default rather + * than required configuration. Overridable per chain for a local or non-standard deployment. + */ +export const DEFAULT_ENDPOINT = '0x6EDCE65403992e310A62460808c4b910D972f10f' + +/** + * A per-token inbound threshold. + * + * `minValue` is a decimal string in the token's smallest unit, not a float — a uint256 does not + * fit a JS number, and rounding a threshold is how a dusting defence silently stops working. + */ +export interface TokenMinimum { + chain: string + token: string + minValue: string +} + +const UINT_DECIMAL = /^\d{1,78}$/ + +/** + * Parse `chain:token:minValue` triples. + * + * Returns problems rather than throwing so `loadConfig` can report them alongside everything + * else — an operator should see every misconfiguration in one boot, not one per restart. + */ +export function parseTokenMinimums(raw: string): { minimums: TokenMinimum[]; problems: string[] } { + const minimums: TokenMinimum[] = [] + const problems: string[] = [] + const seen = new Set() + + for (const part of raw.split(',').map((p) => p.trim()).filter(Boolean)) { + const fields = part.split(':').map((f) => f.trim()) + if (fields.length !== 3) { + problems.push(`TOKEN_MINIMUMS: '${part}' must be chain:token:minValue`) + continue + } + const [chain, tokenRaw, minValue] = fields + const token = tokenRaw.toLowerCase() + + if (!(chain in CHAIN_REGISTRY)) { + problems.push(`TOKEN_MINIMUMS: unknown chain '${chain}' (known: ${Object.keys(CHAIN_REGISTRY).join(', ')})`) + continue + } + if (!EVM_ADDRESS.test(token)) { + problems.push(`TOKEN_MINIMUMS: '${tokenRaw}' is not a 20-byte EVM address`) + continue + } + if (!UINT_DECIMAL.test(minValue)) { + problems.push( + `TOKEN_MINIMUMS: minValue '${minValue}' for ${chain}:${token} must be a decimal integer in the token's smallest unit`, + ) + continue + } + const key = `${chain}|${token}` + if (seen.has(key)) { + problems.push(`TOKEN_MINIMUMS: duplicate entry for ${chain}:${token}`) + continue + } + seen.add(key) + minimums.push({ chain, token, minValue }) + } + return { minimums, problems } +} + +export interface Config { + readonly nodeEnv: string + readonly databaseUrl: string + readonly chains: readonly ResolvedChain[] + readonly pollMs: number + readonly confirmations: number + /** How far back to look on a cold cursor. */ + readonly scanWindow: number + /** Maximum blocks per getLogs call, so a cold start does not ask for a million blocks. */ + readonly scanChunk: number + /** How many blocks to unwind when a reorg is detected. */ + readonly reorgDepth: number + readonly feedSigningKey: string + readonly feedSource: string + readonly feedTtlSec: number + readonly feedRebuildMs: number + readonly policyVersion: number + /** ERC-20 contracts whose Transfer events build the graph. Empty means no edges are built. */ + readonly trackedTokens: readonly string[] + /** Source verifier base URL. Empty disables verification lookups entirely. */ + readonly verifierUrl: string + readonly verifyBatch: number + readonly verifyTtlSec: number + /** + * Minimum transfer value, per chain and token, for an INBOUND edge to count as exposure. + * Without an entry a token is never labelled inbound, so this is what turns the + * `sanctions_1hop_inbound` signal on. + */ + readonly tokenMinimums: readonly TokenMinimum[] + readonly httpPort: number + readonly logLevel: string +} + +function intField(def: number, min: number, max = Number.MAX_SAFE_INTEGER) { + return z.preprocess( + (v) => (v === undefined || v === '' ? def : v), + z.coerce.number().int().min(min).max(max), + ) +} + +const ScalarSchema = z.object({ + NODE_ENV: z.string().optional().default('production'), + DATABASE_URL: z + .string({ error: 'DATABASE_URL is required' }) + .min(1, 'DATABASE_URL is required'), + FEED_SIGNING_KEY: z + .string({ error: 'FEED_SIGNING_KEY is required' }) + .regex(HEX_PRIVATE_KEY, 'FEED_SIGNING_KEY must be a 32-byte hex key (64 hex chars, 0x prefix optional)') + .transform(withHexPrefix), + FEED_SOURCE: z + .string() + .optional() + .transform((v) => (v === undefined || v.trim() === '' ? 'trusted-indexer-a' : v.trim())), + FEED_TTL_SEC: intField(7200, 60), + FEED_REBUILD_MS: intField(600_000, 1000), + // v2: graph proximity extended to 3 hops. Must equal the worker's POLICY_VERSION. + POLICY_VERSION: intField(2, 0), + POLL_MS: intField(15_000, 1), + CONFIRMATIONS: intField(5, 0), + SCAN_BACKFILL_BLOCKS: intField(5000, 1), + SCAN_CHUNK_BLOCKS: intField(2000, 1, 50_000), + REORG_DEPTH: intField(32, 1, 1000), + TRACKED_TOKENS: z + .string() + .optional() + .transform((v) => (v ?? '').trim()), + VERIFIER_URL: z + .string() + .optional() + .transform((v) => (v === undefined ? DEFAULT_SOURCIFY_URL : v.trim())), + VERIFY_BATCH: intField(50, 1, 500), + VERIFY_TTL_SEC: intField(604_800, 60), + TOKEN_MINIMUMS: z + .string() + .optional() + .transform((v) => (v ?? '').trim()), + HTTP_PORT: intField(9091, 1, 65535), + LOG_LEVEL: z + .string() + .optional() + .transform((v) => (v === undefined || v === '' ? 'info' : v)) + .pipe(z.enum(LOG_LEVELS)), +}) + +export class ConfigError extends Error { + constructor(public readonly problems: string[]) { + super(`Invalid indexer configuration:\n${problems.map((p) => ` - ${p}`).join('\n')}`) + this.name = 'ConfigError' + } +} + +/** + * Validate the environment and resolve the active chain set, failing with every problem at once + * so an operator fixes one boot rather than ten. + * + * The feed TTL must comfortably exceed the rebuild interval: if a document could expire before + * its replacement is built, the worker's screening would flap between having feed labels and not. + */ +export function loadConfig(env: NodeJS.ProcessEnv = process.env): Config { + const problems: string[] = [] + + const scalar = ScalarSchema.safeParse(env) + if (!scalar.success) { + for (const issue of scalar.error.issues) { + problems.push(`${issue.path.join('.') || '(root)'}: ${issue.message}`) + } + } + + const known = Object.keys(CHAIN_REGISTRY) + const enabledRaw = (env.CHAINS_ENABLED ?? '').trim() + let enabledKeys: string[] + if (enabledRaw === '') { + enabledKeys = known + } else { + enabledKeys = enabledRaw.split(',').map((s) => s.trim()).filter(Boolean) + const unknown = enabledKeys.filter((k) => !(k in CHAIN_REGISTRY)) + if (unknown.length) { + problems.push(`CHAINS_ENABLED references unknown chain(s): ${unknown.join(', ')} (known: ${known.join(', ')})`) + enabledKeys = enabledKeys.filter((k) => k in CHAIN_REGISTRY) + } + } + + const chains: ResolvedChain[] = [] + for (const key of enabledKeys) { + const s = CHAIN_REGISTRY[key] + const dvn = (env[s.dvnEnv] ?? '').trim() + if (!EVM_ADDRESS.test(dvn)) { + problems.push(`${s.dvnEnv}: required for enabled chain '${key}' and must be a 20-byte EVM address`) + continue + } + const endpoint = (env[`ENDPOINT_${key.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase()}`] ?? '').trim() + if (endpoint && !EVM_ADDRESS.test(endpoint)) { + problems.push(`ENDPOINT override for '${key}' must be a 20-byte EVM address`) + continue + } + chains.push({ + key, + name: s.name, + eid: s.eid, + chainId: s.chainId, + rpc: (env[s.rpcEnv] ?? '').trim() || s.rpcDefault, + dvn, + endpoint: endpoint || DEFAULT_ENDPOINT, + }) + } + + // Read from the raw environment rather than the parsed result: if some other scalar failed we + // still want to report a bad token list now, instead of after the operator fixes that one. + const trackedTokens = (env.TRACKED_TOKENS ?? '') + .split(',') + .map((t) => t.trim().toLowerCase()) + .filter(Boolean) + const badTokens = trackedTokens.filter((t) => !EVM_ADDRESS.test(t)) + if (badTokens.length) problems.push(`TRACKED_TOKENS: not valid EVM addresses: ${badTokens.join(', ')}`) + + const { minimums: tokenMinimums, problems: minimumProblems } = parseTokenMinimums(env.TOKEN_MINIMUMS ?? '') + problems.push(...minimumProblems) + + if (scalar.success && scalar.data.FEED_TTL_SEC * 1000 <= scalar.data.FEED_REBUILD_MS) { + problems.push( + `FEED_TTL_SEC (${scalar.data.FEED_TTL_SEC}s) must exceed FEED_REBUILD_MS (${scalar.data.FEED_REBUILD_MS}ms) — otherwise a feed can expire before its replacement exists`, + ) + } + + if (problems.length) throw new ConfigError(problems) + const d = scalar.data! + + return Object.freeze({ + nodeEnv: d.NODE_ENV, + databaseUrl: d.DATABASE_URL, + chains: Object.freeze(chains), + pollMs: d.POLL_MS, + confirmations: d.CONFIRMATIONS, + scanWindow: d.SCAN_BACKFILL_BLOCKS, + scanChunk: d.SCAN_CHUNK_BLOCKS, + reorgDepth: d.REORG_DEPTH, + feedSigningKey: d.FEED_SIGNING_KEY, + feedSource: d.FEED_SOURCE, + feedTtlSec: d.FEED_TTL_SEC, + feedRebuildMs: d.FEED_REBUILD_MS, + policyVersion: d.POLICY_VERSION, + trackedTokens: Object.freeze(trackedTokens), + verifierUrl: d.VERIFIER_URL, + verifyBatch: d.VERIFY_BATCH, + verifyTtlSec: d.VERIFY_TTL_SEC, + tokenMinimums: Object.freeze(tokenMinimums), + httpPort: d.HTTP_PORT, + logLevel: d.LOG_LEVEL, + }) +} diff --git a/indexer/src/db.ts b/indexer/src/db.ts new file mode 100644 index 0000000..dbdce93 --- /dev/null +++ b/indexer/src/db.ts @@ -0,0 +1,20 @@ +/** + * The narrow database surface the indexer uses. + * + * Everything goes through this interface rather than a `pg.Pool` directly, so the whole data + * layer can be exercised against an in-memory Postgres in tests without a live server. + */ +export interface QueryResult { + rows: T[] + rowCount: number +} + +export interface Db { + query>(sql: string, params?: unknown[]): Promise> + /** Run `fn` inside a transaction, rolling back if it throws. */ + transaction(fn: (tx: Db) => Promise): Promise + close(): Promise +} + +/** Lowercase hex for storage, so no query has to case-fold. */ +export const norm = (s: string): string => s.toLowerCase() diff --git a/indexer/src/feed/builder.ts b/indexer/src/feed/builder.ts new file mode 100644 index 0000000..c225a1b --- /dev/null +++ b/indexer/src/feed/builder.ts @@ -0,0 +1,123 @@ +import { ethers } from 'ethers' + +import { computeProximity } from '../graph/proximity' +import { unverifiedContracts } from '../verify/refresh' + +import { canonicalize } from './canonical' + +import type { Db } from '../db' + +/** + * Builds and signs the document the DVN worker consumes. + * + * The format is fixed by `worker/assess/ingest/feed.ts` and documented in the indexer README. + * Three things about it are load-bearing: + * + * - **Only derived labels are published.** Sanctions and mixer lists are NOT republished: the + * worker reads those first-hand from OFAC/OpenSanctions, and re-feeding them as a + * `trusted_indexer` claim would launder an authoritative source into a derived one. + * - **No `action`, no `confidence`.** Both are the worker's to decide (`SOURCE_TRUST`, + * `ACTION_THRESHOLDS`). Asserting them here would move enforcement authority to the indexer. + * - **No score either.** The worker's `LABEL_WEIGHTS` are authoritative and a derived label + * cannot cause a refusal regardless, so a score would add a number without adding meaning. + */ + +export interface FeedEntry { + address: string + labels: string[] +} + +export interface FeedDocument { + version: number + generatedAt: number + expiresAt: number + source: string + policyVersion: number + entries: FeedEntry[] +} + +export interface SignedFeed extends FeedDocument { + signature: string +} + +export interface BuildFeedOptions { + source: string + policyVersion: number + ttlSec: number + signingKey: string + now?: () => number +} + +/** Next version for a source: strictly increasing, and it must survive a restart. */ +export async function nextVersion(db: Db): Promise { + const res = await db.query<{ max: string | null }>('SELECT max(version) AS max FROM feeds') + return Number(res.rows[0]?.max ?? 0) + 1 +} + +/** + * Every label to publish, merged per address. + * + * `unverified_contract` is only worth publishing where it can change an outcome. On its own it + * scores 20, below the worker's delay threshold, so an address carrying nothing else would be + * feed weight for no effect. It becomes meaningful in combination — with a proximity label here, + * or with the `upgradeable_proxy` the worker observes for itself — so it is published for + * addresses the graph already has something to say about. + */ +export async function collectEntries(db: Db): Promise { + const labelled = await computeProximity(db) + const unverified = new Set(await unverifiedContracts(db)) + + const entries = labelled.map((l) => ({ + address: l.subject, + labels: unverified.has(l.subject) ? [...l.labels, 'unverified_contract'].sort() : l.labels, + })) + return entries +} + +/** Sign the canonical form of everything except `signature` (EIP-191 personal_sign). */ +export async function signFeed(doc: FeedDocument, signingKey: string): Promise { + const wallet = new ethers.Wallet(signingKey) + const signature = await wallet.signMessage(canonicalize(doc)) + return { ...doc, signature } +} + +/** + * Build, sign, and persist the next feed. + * + * Persisting the document is what makes the version counter durable: the worker rejects a + * version it has already accepted, so handing out a repeat after a restart would get the feed + * refused rather than applied. + */ +export async function buildAndPublish(db: Db, opts: BuildFeedOptions): Promise { + const now = opts.now ?? Date.now + const nowSec = Math.floor(now() / 1000) + + const entries = await collectEntries(db) + const version = await nextVersion(db) + + const doc: FeedDocument = { + version, + generatedAt: nowSec, + expiresAt: nowSec + opts.ttlSec, + source: opts.source, + policyVersion: opts.policyVersion, + entries, + } + const signed = await signFeed(doc, opts.signingKey) + + await db.query( + `INSERT INTO feeds (version, generated_at, expires_at, policy_version, entry_count, document) + VALUES ($1, $2, $3, $4, $5, $6)`, + [doc.version, doc.generatedAt, doc.expiresAt, doc.policyVersion, doc.entries.length, JSON.stringify(signed)], + ) + return signed +} + +/** The newest published feed, or undefined before the first build. */ +export async function latestFeed(db: Db): Promise { + const res = await db.query<{ document: string }>( + 'SELECT document FROM feeds ORDER BY version DESC LIMIT 1', + ) + if (!res.rows.length) return undefined + return JSON.parse(res.rows[0].document) as SignedFeed +} diff --git a/indexer/src/feed/canonical.ts b/indexer/src/feed/canonical.ts new file mode 100644 index 0000000..eeba17e --- /dev/null +++ b/indexer/src/feed/canonical.ts @@ -0,0 +1,27 @@ +/** + * Deterministic JSON: object keys sorted, arrays left in order, no whitespace. + * + * MUST stay byte-for-byte identical to `worker/assess/canonical.ts`. The two live in separate + * packages with separate Docker builds, so this is a deliberate copy rather than a shared import; + * `test/canonical.spec.ts` pins the output against the same fixture the worker's suite uses, so + * drift fails a test instead of silently breaking every signature. + * + * Non-integer numbers are rejected because float formatting is not guaranteed to round-trip + * identically across languages — a document carrying one could hash differently on each side. + */ +export function canonicalize(value: unknown): string { + if (value === undefined) throw new Error('cannot canonicalize undefined') + if (value === null || typeof value !== 'object') { + if (typeof value === 'number' && !Number.isInteger(value)) { + throw new Error(`non-integer number cannot be canonicalized: ${value}`) + } + return JSON.stringify(value) + } + if (Array.isArray(value)) return `[${value.map(canonicalize).join(',')}]` + const obj = value as Record + const parts = Object.keys(obj) + .sort() + .filter((k) => obj[k] !== undefined) + .map((k) => `${JSON.stringify(k)}:${canonicalize(obj[k])}`) + return `{${parts.join(',')}}` +} diff --git a/indexer/src/graph/proximity.ts b/indexer/src/graph/proximity.ts new file mode 100644 index 0000000..df9c077 --- /dev/null +++ b/indexer/src/graph/proximity.ts @@ -0,0 +1,195 @@ +import type { Db } from '../db' + +/** + * Proximity to the seed set, up to `GRAPH_DEPTH` hops. + * + * Direction is what makes it dusting-resistant, and the asymmetry is deliberate: + * + * - OUTBOUND (subject -> ... -> seed) starts with the subject's own act. The first edge needs + * no threshold; every edge after it is someone ELSE's act, so it must clear the per-token + * minimum — otherwise anyone the subject ever paid could smear them by dusting a sanctioned + * address. + * - INBOUND (seed -> ... -> subject) is something done TO the subject at every step, so every + * edge must clear the minimum. A token with no configured minimum never extends a path — + * the edge is still stored, it just is not treated as evidence. + * + * A path only counts if the funds could actually have flowed along it: hops stay on one chain + * and block numbers never decrease. It must also be a simple path (no vertex twice), and no + * seed may appear anywhere but the far endpoint — a route through a second sanctioned address + * is that address's shorter proximity, not this one's. + */ + +/** + * Traversal depth. Matches `N_HOP.depth` in the worker's `assess/policy.ts`. + * + * Stated as a constant so the choice is declared rather than implied by the shape of the + * queries below. Changing it is a policy decision, not a refactor: each hop carries its own + * label and weight (evidence weakens fast with distance), so a new depth needs new entries in + * the ladders below AND in the worker's `LABEL_WEIGHTS`/`REASON_BITS`, plus a `POLICY_VERSION` + * bump on both sides. + */ +export const GRAPH_DEPTH = 3 + +/** + * The zero address, excluded from every path. + * + * ERC-20 mints and burns are Transfer events to/from it. Left in, it becomes a hub joining every + * holder to every other one, and paths route THROUGH it: "B burned, then the token minted to C, + * then C paid a sanctioned address" would label B at 3 hops on the strength of two unrelated + * supply events. It is not a counterparty, so it is not a vertex. + * + * A cross-chain send is such a burn/mint pair, which once made bridged transfers invisible here. + * They are no longer inferred from the pair: the source chain's `OFTSent` and `PacketSent` name the + * real sender and recipient, and the scanner stores that as a `bridge` edge between them. + */ +const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' + +export interface GraphLabel { + subject: string + label: string +} + +/** Label per seed class and depth (index = depth - 1). Weights live in the worker's policy. */ +export const OUTBOUND_LABELS: Record = { + sanctions: ['sanctions_1hop', 'sanctions_2hop', 'sanctions_3hop'], + sanctioned_mixer: ['mixer_exposure', 'mixer_exposure_2hop', 'mixer_exposure_3hop'], +} + +export const INBOUND_LABELS: Record = { + sanctions: ['sanctions_1hop_inbound', 'sanctions_2hop_inbound', 'sanctions_3hop_inbound'], + sanctioned_mixer: ['mixer_exposure', 'mixer_exposure_2hop', 'mixer_exposure_3hop'], +} + +type Direction = 'outbound' | 'inbound' + +/** + * One direction-and-depth path query, assembled rather than written out six times. + * + * Kept to plain joins and LEFT JOIN anti-joins on purpose: the in-memory Postgres the tests run + * against does not execute recursive CTEs, and at depth <= 3 the explicit form is also the one + * the planner handles predictably. Vertices are v0 = e1.from_addr and vi = ei.to_addr. + */ +function pathSql(direction: Direction, depth: number): string { + const vertex = (i: number) => (i === 0 ? 'e1.from_addr' : `e${i}.to_addr`) + const seedVertex = direction === 'outbound' ? depth : 0 + const subject = direction === 'outbound' ? vertex(0) : vertex(depth) + + const joins: string[] = [] + const where: string[] = [] + + for (let i = 2; i <= depth; i++) { + // Same chain, forward in time: the path must be one funds could actually have taken. + // + // A bridge edge is never continued. Its `to_addr` received on ANOTHER chain, so that address's + // activity here is somebody else's money — following it would invent a path. A bridge edge can + // therefore only be a path's last hop (or its only one), which is where the evidence is anyway: + // the subject's own act of sending toward a seed. Continuing across the bridge properly needs + // per-edge timestamps, since block numbers do not compare between chains. + joins.push( + `JOIN edges e${i} ON e${i}.chain = e${i - 1}.chain AND e${i}.from_addr = e${i - 1}.to_addr` + + ` AND e${i}.block_number >= e${i - 1}.block_number AND e${i - 1}.kind <> 'bridge'`, + ) + } + for (let i = 1; i <= depth; i++) { + // The minimum applies to every edge that is not the subject's own act. + if (direction === 'inbound' || i > 1) { + joins.push( + `JOIN token_minimums m${i} ON m${i}.chain = e${i}.chain AND m${i}.token = e${i}.token` + + ` AND e${i}.value >= m${i}.min_value`, + ) + } + } + + joins.push(`JOIN seed_labels s ON s.subject = ${vertex(seedVertex)}`) + + // Simple path: every vertex distinct from every other, and never mint/burn. + for (let a = 0; a <= depth; a++) { + where.push(`${vertex(a)} <> '${ZERO_ADDRESS}'`) + for (let b = a + 1; b <= depth; b++) where.push(`${vertex(a)} <> ${vertex(b)}`) + } + + // No seed anywhere but the far endpoint. + let z = 0 + for (let i = 0; i <= depth; i++) { + if (i === seedVertex) continue + z++ + joins.push(`LEFT JOIN seed_labels z${z} ON z${z}.subject = ${vertex(i)}`) + where.push(`z${z}.subject IS NULL`) + } + + return `SELECT DISTINCT ${subject} AS subject, s.label AS seed_label + FROM edges e1 + ${joins.join('\n ')} + WHERE ${where.join('\n AND ')}` +} + +/** + * Labels for one direction, shortest distance first. + * + * A subject with both a direct edge and a longer route to the same seed class is labelled at + * its closest approach only — the label states how near the subject came, and scoring the same + * fact once per route would inflate it. + */ +async function pathLabels(db: Db, direction: Direction): Promise { + const ladder = direction === 'outbound' ? OUTBOUND_LABELS : INBOUND_LABELS + const best = new Map() + for (let depth = 1; depth <= GRAPH_DEPTH; depth++) { + const res = await db.query<{ subject: string; seed_label: string }>(pathSql(direction, depth)) + for (const row of res.rows) { + const key = `${row.subject}|${row.seed_label}` + if (!best.has(key)) best.set(key, depth) // ascending depth loop -> first hit is shortest + } + } + + const out: GraphLabel[] = [] + for (const [key, depth] of best) { + const [subject, seedLabel] = key.split('|') + const label = ladder[seedLabel]?.[depth - 1] + if (label) out.push({ subject, label }) + } + return out +} + +/** Subjects that sent toward a seed within GRAPH_DEPTH hops. */ +export function outboundLabels(db: Db): Promise { + return pathLabels(db, 'outbound') +} + +/** Subjects that received from a seed within GRAPH_DEPTH hops, every edge above its minimum. */ +export function inboundLabels(db: Db): Promise { + return pathLabels(db, 'inbound') +} + +/** All proximity labels, merged so each subject appears once with its full label set. */ +export async function computeProximity(db: Db): Promise> { + const [outbound, inbound] = await Promise.all([outboundLabels(db), inboundLabels(db)]) + const bySubject = new Map>() + for (const { subject, label } of [...outbound, ...inbound]) { + const set = bySubject.get(subject) ?? new Set() + set.add(label) + bySubject.set(subject, set) + } + return [...bySubject.entries()] + .map(([subject, labels]) => ({ subject, labels: [...labels].sort() })) + .sort((a, b) => (a.subject < b.subject ? -1 : a.subject > b.subject ? 1 : 0)) +} + +/** How many distinct seeds a subject touched directly (depth 1), for dashboards. */ +export async function exposureCounts(db: Db): Promise> { + const res = await db.query<{ subject: string; seeds: string }>( + `SELECT subject, count(DISTINCT seed) AS seeds FROM ( + SELECT e.from_addr AS subject, e.to_addr AS seed + FROM edges e JOIN seed_labels s ON s.subject = e.to_addr + WHERE e.from_addr <> '${ZERO_ADDRESS}' + UNION + SELECT e.to_addr AS subject, e.from_addr AS seed + FROM edges e + JOIN seed_labels s ON s.subject = e.from_addr + JOIN token_minimums m ON m.chain = e.chain AND m.token = e.token + WHERE e.value >= m.min_value + AND e.to_addr <> '${ZERO_ADDRESS}' + ) t + GROUP BY subject`, + ) + return res.rows.map((r) => ({ subject: r.subject, seeds: Number(r.seeds) })) +} diff --git a/indexer/src/http/server.ts b/indexer/src/http/server.ts new file mode 100644 index 0000000..a1f2d19 --- /dev/null +++ b/indexer/src/http/server.ts @@ -0,0 +1,212 @@ +import { type Server, createServer } from 'http' + +import { computeProximity } from '../graph/proximity' + +import type { Db } from '../db' +import type { SignedFeed } from '../feed/builder' +import type { Metrics } from '../metrics' +import type { Logger } from 'pino' + + +export interface HttpDeps { + port: number + metrics: Metrics + logger: Logger + /** The newest published feed, or undefined before the first build. */ + feed: () => Promise + /** Whether the ingest loop is healthy enough to serve. */ + isReady: () => boolean + /** Read-only queries for the dashboard API. */ + db: Db + /** Static configuration echoed by /api/status, so the dashboard shows what this instance watches. */ + statusInfo: () => Record +} + +export interface HttpServer { + server: Server + port: number + close: () => Promise +} + +/** Clamp a ?limit= parameter to something a browser table can actually render. */ +function limitParam(params: URLSearchParams, def: number, max: number): number { + const n = Number(params.get('limit') ?? def) + return Number.isInteger(n) && n > 0 ? Math.min(n, max) : def +} + +const HEX_ADDRESS = /^0x[0-9a-fA-F]{40}$/ +const HEX32 = /^0x[0-9a-fA-F]{64}$/ + +/** + * Serves the feed, the usual operational endpoints, and a read-only JSON API for the demo + * dashboard (/api/*). + * + * Everything here is public by design — the feed is signed, so its integrity does not depend on + * who can read it, and the API rows are already on-chain or derived from public lists. That also + * means the signing key must never be reachable from here, and every response carries a + * permissive CORS header so a browser dashboard can read it directly. + */ +export async function startHttpServer(deps: HttpDeps): Promise { + const server = createServer((req, res) => { + const [path, query] = (req.url ?? '/').split('?') + const params = new URLSearchParams(query ?? '') + + const send = (status: number, body: string, contentType = 'text/plain; charset=utf-8') => { + res.writeHead(status, { + 'content-type': contentType, + 'cache-control': 'no-store', + 'access-control-allow-origin': '*', + }) + res.end(body) + } + const sendJson = (status: number, body: unknown) => + send(status, JSON.stringify(body), 'application/json; charset=utf-8') + const fail = (what: string) => (err: Error) => { + deps.logger.error({ err: err.message, path }, `${what} failed`) + sendJson(500, { error: `${what} unavailable` }) + } + + if (path === '/healthz') return send(200, 'ok') + if (path === '/readyz') return deps.isReady() ? send(200, 'ready') : send(503, 'not ready') + + if (path === '/metrics') { + deps.metrics.registry + .metrics() + .then((text) => send(200, text, deps.metrics.registry.contentType)) + .catch(fail('metrics render')) + return + } + + if (path === '/feed/latest.json') { + deps + .feed() + .then((feed) => + feed + ? sendJson(200, feed) + : // 503 rather than an empty feed: "no labels yet" and "no data available" must not + // look the same to a consumer that fails closed on staleness. + sendJson(503, { error: 'no feed published yet' }), + ) + .catch(fail('feed read')) + return + } + + // ── Read-only dashboard API ───────────────────────────────────────────── + if (path === '/api/status') { + Promise.all([deps.feed(), deps.db.query<{ n: string }>('SELECT count(*) AS n FROM seed_labels')]) + .then(([feed, seeds]) => + sendJson(200, { + ...deps.statusInfo(), + seeds: Number(seeds.rows[0]?.n ?? 0), + feed: feed + ? { + version: feed.version, + entries: feed.entries.length, + policyVersion: feed.policyVersion, + generatedAt: feed.generatedAt, + expiresAt: feed.expiresAt, + } + : null, + }), + ) + .catch(fail('status')) + return + } + + if (path === '/api/seeds') { + const q = (params.get('q') ?? '').toLowerCase() + deps.db + .query<{ subject: string; label: string; source: string }>( + q + ? `SELECT subject, label, source FROM seed_labels WHERE subject LIKE $1 ORDER BY label, subject LIMIT $2` + : `SELECT subject, label, source FROM seed_labels ORDER BY label, subject LIMIT $1`, + q ? [`%${q}%`, limitParam(params, 1000, 5000)] : [limitParam(params, 1000, 5000)], + ) + .then((r) => sendJson(200, { seeds: r.rows })) + .catch(fail('seeds query')) + return + } + + if (path === '/api/verdicts') { + const payloadHash = (params.get('payloadHash') ?? '').toLowerCase() + if (payloadHash && !HEX32.test(payloadHash)) return sendJson(400, { error: 'payloadHash must be 0x + 64 hex' }) + // LEFT JOIN: a verdict whose block row is missing still shows, just without a timestamp. + const base = `SELECT v.chain, v.block_number, v.tx_hash, v.payload_hash, v.action, v.score, + v.reason_mask, v.evidence_hash, b.block_time + FROM risk_verdicts v + LEFT JOIN blocks b ON b.chain = v.chain AND b.number = v.block_number` + deps.db + .query( + payloadHash + ? `${base} WHERE v.payload_hash = $1 ORDER BY v.block_number DESC LIMIT $2` + : `${base} ORDER BY v.block_number DESC LIMIT $1`, + payloadHash ? [payloadHash, limitParam(params, 200, 1000)] : [limitParam(params, 200, 1000)], + ) + .then((r) => sendJson(200, { verdicts: r.rows })) + .catch(fail('verdicts query')) + return + } + + if (path === '/api/approvals') { + deps.db + .query( + `SELECT a.chain, a.block_number, a.tx_hash, a.payload_hash, a.approver, b.block_time + FROM packet_approvals a + LEFT JOIN blocks b ON b.chain = a.chain AND b.number = a.block_number + ORDER BY a.block_number DESC LIMIT $1`, + [limitParam(params, 200, 1000)], + ) + .then((r) => sendJson(200, { approvals: r.rows })) + .catch(fail('approvals query')) + return + } + + if (path === '/api/edges') { + const address = (params.get('address') ?? '').toLowerCase() + if (address && !HEX_ADDRESS.test(address)) return sendJson(400, { error: 'address must be 0x + 40 hex' }) + const base = `SELECT e.chain, e.block_number, e.tx_hash, e.token, e.from_addr, e.to_addr, e.value, + e.kind, e.dst_chain, b.block_time + FROM edges e + LEFT JOIN blocks b ON b.chain = e.chain AND b.number = e.block_number` + deps.db + .query( + address + ? `${base} WHERE e.from_addr = $1 OR e.to_addr = $1 ORDER BY e.block_number DESC LIMIT $2` + : `${base} ORDER BY e.block_number DESC LIMIT $1`, + address ? [address, limitParam(params, 500, 2000)] : [limitParam(params, 500, 2000)], + ) + .then((r) => sendJson(200, { edges: r.rows })) + .catch(fail('edges query')) + return + } + + if (path === '/api/proximity') { + // Live recomputation, not the published feed: the dashboard's graph should show what the + // NEXT feed will say, without waiting out the rebuild interval. + computeProximity(deps.db) + .then((labelled) => sendJson(200, { labelled })) + .catch(fail('proximity query')) + return + } + + send(404, 'not found') + }) + + await new Promise((resolve, reject) => { + server.once('error', reject) + server.listen(deps.port, () => { + server.removeListener('error', reject) + resolve() + }) + }) + + const address = server.address() + const port = typeof address === 'object' && address ? address.port : deps.port + deps.logger.info({ port }, 'http server listening (/feed/latest.json /healthz /readyz /metrics /api/*)') + + return { + server, + port, + close: () => new Promise((resolve) => server.close(() => resolve())), + } +} diff --git a/indexer/src/ingest/scanner.ts b/indexer/src/ingest/scanner.ts new file mode 100644 index 0000000..1c4e2c3 --- /dev/null +++ b/indexer/src/ingest/scanner.ts @@ -0,0 +1,173 @@ +import { scanBridgeSends, scanDvnEvents, scanTransfers } from '../chain/events' + +import type { IngestStore } from './store' +import type { BridgeSendRow, LogSource, PacketApprovalRow, RiskVerdictRow, TransferRow } from '../chain/events' +import type { Logger } from 'pino' + +export interface ScanChainDeps { + chain: { key: string; dvn: string; endpoint: string } + source: LogSource + store: IngestStore + trackedTokens: readonly string[] + /** + * LayerZero eid -> chain key, for naming where a cross-chain send was addressed. A destination + * this deployment does not index resolves to undefined, and the edge is still recorded — the + * sender's act is the evidence, and it happened here. + */ + chainByEid?: (eid: number) => string | undefined + confirmations: number + /** How far back to start on a cold cursor. */ + scanWindow: number + /** Maximum blocks per getLogs range. */ + scanChunk: number + /** How far to unwind when the chain no longer matches what we stored. */ + reorgDepth: number + logger: Logger +} + +export interface ScanResult { + from: number + to: number + verdicts: number + approvals: number + transfers: number + bridgeSends: number + reorgDepth: number + /** Chain head as observed this tick — the only place it is read, and scan lag needs it. */ + head: number +} + +/** + * Detect a reorg and decide where to resume from, or undefined if the chain is unchanged. + * + * Comparing the recorded hash for a height against the node's current hash for that same height + * is the only reliable signal — block numbers alone cannot tell you the chain was rewritten. + * + * On a mismatch we unwind a fixed `reorgDepth` window rather than searching for the exact fork + * point. Only blocks we actually touched are recorded, so a search has no dense history to walk + * and would stop early, leaving rows from reorged-away blocks in place. Rescanning a bounded + * window is cheap and provably clears anything the rewrite invalidated. + * + * Whether the rewrite is deeper than that window is answered with a real anchor: the deepest + * block we still have a record for below the window. If even that disagrees, rescanning the + * window would leave stale rows underneath it, so we stop and say so. + */ +async function findReorg(deps: ScanChainDeps, cursor: number): Promise { + const { chain, source, store, logger } = deps + + const stored = await store.getBlockHash(chain.key, cursor) + if (!stored) return undefined // nothing recorded to compare (cold start) + const live = await source.getBlock(cursor) + if (!live) return undefined // cursor above the current head; nothing to conclude + if (live.hash.toLowerCase() === stored.hash) return undefined + + logger.warn( + { chain: chain.key, height: cursor, stored: stored.hash, live: live.hash.toLowerCase() }, + 'reorg: cursor block hash no longer matches', + ) + + const resumeFrom = Math.max(0, cursor - deps.reorgDepth + 1) + if (resumeFrom > 0) { + const anchor = await store.latestBlockBelow(chain.key, resumeFrom) + if (anchor) { + const anchorLive = await source.getBlock(anchor.number) + if (anchorLive && anchorLive.hash.toLowerCase() !== anchor.hash) { + throw new Error( + `reorg deeper than REORG_DEPTH (${deps.reorgDepth}) on ${chain.key}: block ${anchor.number} also changed`, + ) + } + } + } + return resumeFrom +} + +/** + * Scan one chain once: handle any reorg, then ingest up to the safe head in bounded chunks. + * + * Only blocks below `head - confirmations` are read, which makes reorgs rare; the detection above + * exists because rare is not never. The cursor advances per committed chunk, so an interruption + * costs one chunk of rework rather than the whole range. + */ +export async function scanChainOnce(deps: ScanChainDeps): Promise { + const { chain, source, store, logger } = deps + const log = logger.child({ chain: chain.key }) + + const head = await source.getBlockNumber() + const safeHead = head - deps.confirmations + const stored = await store.getCursor(chain.key) + + let reorgUnwound = 0 + let cursor = stored ?? Math.max(0, safeHead - deps.scanWindow) + if (stored !== undefined && stored > 0) { + const resumeFrom = await findReorg(deps, stored) + if (resumeFrom !== undefined) { + const deleted = await store.rollback(chain.key, resumeFrom) + reorgUnwound = stored - resumeFrom + 1 + cursor = resumeFrom - 1 + log.warn({ resumeFrom, deleted, unwound: reorgUnwound }, 'reorg: rolled back and will rescan') + } + } + + const result: ScanResult = { from: cursor + 1, to: cursor, verdicts: 0, approvals: 0, transfers: 0, bridgeSends: 0, reorgDepth: reorgUnwound, head } + if (safeHead <= cursor) return result + + while (cursor < safeHead) { + const from = cursor + 1 + const to = Math.min(from + deps.scanChunk - 1, safeHead) + + const skipped = new Map() + const unpaired: string[] = [] + const [{ verdicts, approvals }, transfers, sends] = await Promise.all([ + scanDvnEvents(source, chain.dvn, from, to), + scanTransfers(source, deps.trackedTokens, from, to, (token) => + skipped.set(token, (skipped.get(token) ?? 0) + 1), + ), + scanBridgeSends(source, deps.trackedTokens, chain.endpoint, from, to, (guid) => unpaired.push(guid)), + ]) + const bridgeSends = sends.map((s) => ({ ...s, dstChain: deps.chainByEid?.(s.dstEid) })) + if (unpaired.length) { + // Loud, because a send whose recipient could not be read is a missing edge, not a missing row. + log.warn({ from, to, unpaired }, 'cross-chain sends with no readable packet — recipient unknown, edge skipped') + } + if (skipped.size) { + log.warn( + { from, to, skipped: Object.fromEntries(skipped) }, + 'skipped undecodable Transfer logs — is every TRACKED_TOKENS entry an ERC-20?', + ) + } + + // Record identity only for blocks we actually touched — enough for the reorg check at the + // cursor without storing every empty block on the chain. + const heights = new Set([to, ...verdicts.map((v) => v.blockNumber), ...approvals.map((a) => a.blockNumber), ...transfers.map((t) => t.blockNumber), ...bridgeSends.map((b) => b.blockNumber)]) + const blocks: Array<{ number: number; hash: string; parentHash: string; timestamp: number }> = [] + for (const number of heights) { + const block = await source.getBlock(number) + if (block) blocks.push({ number, hash: block.hash, parentHash: block.parentHash, timestamp: block.timestamp }) + } + + await store.commitRange(chain.key, to, { verdicts, approvals, transfers, bridgeSends, blocks }) + result.verdicts += verdicts.length + result.approvals += approvals.length + result.transfers += transfers.length + result.bridgeSends += bridgeSends.length + result.to = to + cursor = to + } + + if (result.verdicts || result.approvals || result.transfers || result.bridgeSends) { + log.info( + { + from: result.from, + to: result.to, + verdicts: result.verdicts, + approvals: result.approvals, + transfers: result.transfers, + bridgeSends: result.bridgeSends, + }, + 'scanned', + ) + } + return result +} + +export type { RiskVerdictRow, PacketApprovalRow, TransferRow, BridgeSendRow } diff --git a/indexer/src/ingest/seeds.ts b/indexer/src/ingest/seeds.ts new file mode 100644 index 0000000..247ccf1 --- /dev/null +++ b/indexer/src/ingest/seeds.ts @@ -0,0 +1,78 @@ +import type { IngestStore } from './store' + +/** + * Authoritative seed labels the graph is grown from. + * + * The indexer reads the same OFAC / OpenSanctions lists the worker does. It does NOT republish + * them — the worker already has them first-hand, and re-feeding a sanctions label back as a + * `trusted_indexer` claim would launder an authoritative source into a derived one. They exist + * here only as the seed set for proximity. + */ + +const OFAC_ETH_URL = + 'https://raw.githubusercontent.com/0xB10C/ofac-sanctioned-digital-currency-addresses/lists/sanctioned_addresses_ETH.json' +const OS_OFAC_SDN_URL = 'https://data.opensanctions.org/datasets/latest/us_ofac_sdn/entities.ftm.json' + +/** Curated OFAC-sanctioned Tornado Cash contracts — same set the worker carries. */ +export const MIXER_ADDRESSES: string[] = [ + '0x722122df12d4e14e13ac3b6895a86e84145b6967', + '0xd90e2f925da726b50c4ed8d0fb90ad053324f31b', + '0x910cbd523d972eb0a6f4cae4618ad62622b39dbf', +].map((a) => a.toLowerCase()) + +export type Fetcher = (url: string) => Promise + +const isHex = (s: string) => /^0x[0-9a-fA-F]+$/.test(s) +const isEvmAddress = (s: string) => /^0x[0-9a-fA-F]{40}$/.test(s) + +export function parseOfacList(body: string): string[] { + const arr = JSON.parse(body) as unknown[] + return arr + .filter((x): x is string => typeof x === 'string') + .map((s) => s.toLowerCase()) + .filter(isHex) +} + +export function parseOpenSanctionsNdjson(body: string): string[] { + const out = new Set() + for (const line of body.split('\n')) { + const t = line.trim() + if (!t) continue + let obj: { schema?: string; properties?: { publicKey?: unknown } } + try { + obj = JSON.parse(t) + } catch { + continue + } + if (obj?.schema !== 'CryptoWallet') continue + const keys = obj?.properties?.publicKey + if (!Array.isArray(keys)) continue + for (const k of keys) { + if (typeof k === 'string' && isEvmAddress(k)) out.add(k.toLowerCase()) + } + } + return [...out] +} + +const defaultFetch: Fetcher = async (url) => { + const fetch = (await import('node-fetch')).default + const res = await fetch(url) + if (!res.ok) throw new Error(`seed fetch failed (${res.status}): ${url}`) + return res.text() +} + +/** + * Refresh the seed set. Seeds are replaced per source rather than merged, so an address removed + * upstream stops seeding proximity instead of lingering forever. + */ +export async function refreshSeeds(store: IngestStore, fetcher: Fetcher = defaultFetch): Promise { + const [ofacBody, osBody] = await Promise.all([fetcher(OFAC_ETH_URL), fetcher(OS_OFAC_SDN_URL)]) + + const ofac = parseOfacList(ofacBody).map((subject) => ({ subject, label: 'sanctions' })) + const os = parseOpenSanctionsNdjson(osBody).map((subject) => ({ subject, label: 'sanctions' })) + const mixers = MIXER_ADDRESSES.map((subject) => ({ subject, label: 'sanctioned_mixer' })) + + await store.replaceSeeds('ofac', [...ofac, ...mixers]) + await store.replaceSeeds('opensanctions', os) + return ofac.length + os.length + mixers.length +} diff --git a/indexer/src/ingest/store.ts b/indexer/src/ingest/store.ts new file mode 100644 index 0000000..c72c865 --- /dev/null +++ b/indexer/src/ingest/store.ts @@ -0,0 +1,197 @@ +import type { BridgeSendRow, PacketApprovalRow, RiskVerdictRow, TransferRow } from '../chain/events' +import type { Db } from '../db' + +/** + * All writes for one chain's scanned range, plus the reorg bookkeeping around them. + * + * Every insert is `ON CONFLICT DO NOTHING` keyed on (chain, tx_hash, log_index): re-scanning a + * range is idempotent, which is what makes a rollback-and-rescan safe rather than duplicating + * rows. + */ +export class IngestStore { + constructor(private readonly db: Db) {} + + async getCursor(chain: string): Promise { + const res = await this.db.query<{ last_block: string }>( + 'SELECT last_block FROM scan_cursor WHERE chain = $1', + [chain], + ) + return res.rows.length ? Number(res.rows[0].last_block) : undefined + } + + async setCursor(chain: string, block: number, db: Db = this.db): Promise { + await db.query( + `INSERT INTO scan_cursor (chain, last_block) VALUES ($1, $2) + ON CONFLICT (chain) DO UPDATE SET last_block = EXCLUDED.last_block`, + [chain, block], + ) + } + + async getBlockHash(chain: string, number: number): Promise<{ hash: string; parentHash: string } | undefined> { + const res = await this.db.query<{ hash: string; parent_hash: string }>( + 'SELECT hash, parent_hash FROM blocks WHERE chain = $1 AND number = $2', + [chain, number], + ) + if (!res.rows.length) return undefined + return { hash: res.rows[0].hash, parentHash: res.rows[0].parent_hash } + } + + /** + * The deepest block we recorded strictly below `height`. + * + * Only blocks we actually touched are recorded, so the reorg check cannot assume a record + * exists at any given height. This gives it a real anchor to compare against instead. + */ + async latestBlockBelow( + chain: string, + height: number, + ): Promise<{ number: number; hash: string } | undefined> { + const res = await this.db.query<{ number: string; hash: string }>( + 'SELECT number, hash FROM blocks WHERE chain = $1 AND number < $2 ORDER BY number DESC LIMIT 1', + [chain, height], + ) + if (!res.rows.length) return undefined + return { number: Number(res.rows[0].number), hash: res.rows[0].hash } + } + + async recordBlock( + chain: string, + number: number, + hash: string, + parentHash: string, + blockTime: number, + db: Db = this.db, + ): Promise { + await db.query( + `INSERT INTO blocks (chain, number, hash, parent_hash, block_time) VALUES ($1, $2, $3, $4, $5) + ON CONFLICT (chain, number) DO UPDATE + SET hash = EXCLUDED.hash, parent_hash = EXCLUDED.parent_hash, block_time = EXCLUDED.block_time`, + [chain, number, hash.toLowerCase(), parentHash.toLowerCase(), blockTime], + ) + } + + /** + * Discard everything at or above `fromBlock` for a chain. + * + * A reorg does not "update" rows, it invalidates them: the transactions that produced them may + * simply not exist on the new canonical chain. Deleting and rescanning is the only correct + * treatment, and it is cheap because the range is bounded by `REORG_DEPTH`. + */ + async rollback(chain: string, fromBlock: number): Promise { + return this.db.transaction(async (tx) => { + let deleted = 0 + for (const table of ['edges', 'risk_verdicts', 'packet_approvals', 'blocks']) { + const column = table === 'blocks' ? 'number' : 'block_number' + const res = await tx.query(`DELETE FROM ${table} WHERE chain = $1 AND ${column} >= $2`, [chain, fromBlock]) + deleted += res.rowCount + } + await this.setCursor(chain, Math.max(0, fromBlock - 1), tx) + return deleted + }) + } + + /** Commit one scanned range atomically: rows and the cursor move together or not at all. */ + async commitRange( + chain: string, + toBlock: number, + data: { + verdicts: RiskVerdictRow[] + approvals: PacketApprovalRow[] + transfers: TransferRow[] + /** Cross-chain sends. Each carries the destination chain key, resolved by the caller. */ + bridgeSends?: Array + blocks: Array<{ number: number; hash: string; parentHash: string; timestamp: number }> + }, + ): Promise { + await this.db.transaction(async (tx) => { + for (const b of data.blocks) await this.recordBlock(chain, b.number, b.hash, b.parentHash, b.timestamp, tx) + + for (const v of data.verdicts) { + await tx.query( + `INSERT INTO risk_verdicts + (chain, block_number, tx_hash, log_index, payload_hash, action, score, reason_mask, evidence_hash) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) + ON CONFLICT (chain, tx_hash, log_index) DO NOTHING`, + [chain, v.blockNumber, v.txHash, v.logIndex, v.payloadHash, v.action, v.score, v.reasonMask, v.evidenceHash], + ) + } + + for (const a of data.approvals) { + await tx.query( + `INSERT INTO packet_approvals (chain, block_number, tx_hash, log_index, payload_hash, approver) + VALUES ($1, $2, $3, $4, $5, $6) + ON CONFLICT (chain, tx_hash, log_index) DO NOTHING`, + [chain, a.blockNumber, a.txHash, a.logIndex, a.payloadHash, a.approver], + ) + } + + for (const t of data.transfers) { + await tx.query( + `INSERT INTO edges (chain, block_number, tx_hash, log_index, token, from_addr, to_addr, value) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + ON CONFLICT (chain, tx_hash, log_index) DO NOTHING`, + [chain, t.blockNumber, t.txHash, t.logIndex, t.token, t.from, t.to, t.value], + ) + } + + // Same table, so every graph query sees them without knowing they exist; `kind` keeps a + // bridged send distinguishable from a settled same-chain transfer. The OFTSent log has its + // own index within the transaction, so it never collides with the burn recorded above. + for (const b of data.bridgeSends ?? []) { + await tx.query( + `INSERT INTO edges (chain, block_number, tx_hash, log_index, token, from_addr, to_addr, value, kind, dst_chain) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 'bridge', $9) + ON CONFLICT (chain, tx_hash, log_index) DO NOTHING`, + [chain, b.blockNumber, b.txHash, b.logIndex, b.token, b.from, b.to, b.value, b.dstChain ?? null], + ) + } + + await this.setCursor(chain, toBlock, tx) + }) + } + + /** Replace the authoritative seed labels wholesale — a removed sanction must disappear. */ + async replaceSeeds(source: string, seeds: Array<{ subject: string; label: string }>): Promise { + return this.db.transaction(async (tx) => { + await tx.query('DELETE FROM seed_labels WHERE source = $1', [source]) + for (const s of seeds) { + await tx.query( + `INSERT INTO seed_labels (subject, label, source) VALUES ($1, $2, $3) + ON CONFLICT (subject, label, source) DO NOTHING`, + [s.subject.toLowerCase(), s.label, source], + ) + } + return seeds.length + }) + } + + /** + * Replace the inbound thresholds wholesale from configuration. + * + * A replace, not an upsert: config is the only source of these values, so removing an entry + * from the environment must actually remove the threshold. An upsert would leave a stale + * minimum applying to a token the operator thought they had stopped labelling. + */ + async replaceTokenMinimums(minimums: ReadonlyArray<{ chain: string; token: string; minValue: string }>): Promise { + return this.db.transaction(async (tx) => { + await tx.query('DELETE FROM token_minimums') + for (const m of minimums) { + await tx.query('INSERT INTO token_minimums (chain, token, min_value) VALUES ($1, $2, $3)', [ + m.chain, + m.token.toLowerCase(), + m.minValue, + ]) + } + return minimums.length + }) + } + + async counts(): Promise> { + const out: Record = {} + for (const table of ['edges', 'risk_verdicts', 'packet_approvals', 'seed_labels']) { + const res = await this.db.query<{ n: string }>(`SELECT count(*) AS n FROM ${table}`) + out[table] = Number(res.rows[0]?.n ?? 0) + } + return out + } +} diff --git a/indexer/src/logger.ts b/indexer/src/logger.ts new file mode 100644 index 0000000..c0c9b1e --- /dev/null +++ b/indexer/src/logger.ts @@ -0,0 +1,13 @@ +import pino, { type Logger } from 'pino' + +import type { Config } from './config' + +/** JSON logs in production; pretty only when explicitly developing. */ +export function createLogger(config: Pick): Logger { + const pretty = config.nodeEnv === 'development' + return pino({ + level: config.logLevel, + base: { service: 'compliance-dvn-indexer' }, + ...(pretty ? { transport: { target: 'pino-pretty', options: { colorize: true } } } : {}), + }) +} diff --git a/indexer/src/metrics.ts b/indexer/src/metrics.ts new file mode 100644 index 0000000..9e6c0da --- /dev/null +++ b/indexer/src/metrics.ts @@ -0,0 +1,69 @@ +import { Counter, Gauge, Registry, collectDefaultMetrics } from 'prom-client' + +/** Typed metric surface. Each instance owns a private Registry so tests stay isolated. */ +export interface Metrics { + readonly registry: Registry + + readonly up: Gauge + readonly chainHeadBlock: Gauge<'chain'> + readonly cursorBlock: Gauge<'chain'> + readonly scanErrors: Counter<'chain'> + /** Reorgs observed, by how deep they went — a spike here questions every derived label. */ + readonly reorgs: Counter<'chain'> + readonly reorgBlocksUnwound: Counter<'chain'> + + readonly verdictsIngested: Counter<'chain'> + readonly approvalsIngested: Counter<'chain'> + readonly edgesIngested: Counter<'chain'> + + readonly seedCount: Gauge + readonly seedRefreshTotal: Counter<'result'> + + readonly verificationChecked: Counter<'chain'> + readonly verificationUnverified: Gauge<'chain'> + + readonly feedVersion: Gauge + readonly feedEntries: Gauge + readonly feedGeneratedAt: Gauge + readonly feedBuildTotal: Counter<'result'> +} + +export function createMetrics(): Metrics { + const registry = new Registry() + registry.setDefaultLabels({ service: 'compliance-dvn-indexer' }) + collectDefaultMetrics({ register: registry, prefix: 'indexer_node_' }) + + const g = (name: string, help: string, labelNames: T[] = [] as T[]) => + new Gauge({ name, help, labelNames, registers: [registry] }) + const c = (name: string, help: string, labelNames: T[] = [] as T[]) => + new Counter({ name, help, labelNames, registers: [registry] }) + + return { + registry, + up: g('indexer_up', 'Process is alive (1).'), + chainHeadBlock: g('indexer_chain_head_block', 'Latest block height observed per chain.', ['chain']), + cursorBlock: g('indexer_cursor_block', 'Last committed block per chain.', ['chain']), + scanErrors: c('indexer_scan_errors_total', 'Scan/RPC errors per chain.', ['chain']), + reorgs: c('indexer_reorgs_total', 'Reorgs detected per chain.', ['chain']), + reorgBlocksUnwound: c('indexer_reorg_blocks_unwound_total', 'Blocks rolled back due to reorgs.', ['chain']), + + verdictsIngested: c('indexer_verdicts_ingested_total', 'RiskVerdict events stored.', ['chain']), + approvalsIngested: c('indexer_approvals_ingested_total', 'PacketApproved events stored.', ['chain']), + edgesIngested: c('indexer_edges_ingested_total', 'Transfer edges stored.', ['chain']), + + seedCount: g('indexer_seed_labels', 'Authoritative seed labels currently held.'), + seedRefreshTotal: c('indexer_seed_refresh_total', 'Seed refresh attempts by result.', ['result']), + + verificationChecked: c('indexer_verification_checked_total', 'Addresses resolved for source verification.', ['chain']), + verificationUnverified: g( + 'indexer_verification_unverified', + 'Contracts the verifier positively reported as unverified in the last pass.', + ['chain'], + ), + + feedVersion: g('indexer_feed_version', 'Version of the most recently published feed.'), + feedEntries: g('indexer_feed_entries', 'Entry count in the most recently published feed.'), + feedGeneratedAt: g('indexer_feed_generated_at', 'Unix seconds the newest feed was generated.'), + feedBuildTotal: c('indexer_feed_build_total', 'Feed builds by result.', ['result']), + } +} diff --git a/indexer/src/migrate.ts b/indexer/src/migrate.ts new file mode 100644 index 0000000..1699a41 --- /dev/null +++ b/indexer/src/migrate.ts @@ -0,0 +1,59 @@ +import { readFileSync, readdirSync } from 'fs' +import { join } from 'path' + +import type { Db } from './db' + +const MIGRATIONS_DIR = join(__dirname, '..', 'db', 'migrations') + +/** + * Apply every migration not yet recorded, in filename order. + * + * Each file runs inside its own transaction together with the row that records it, so a failure + * leaves neither a half-applied schema nor a schema that claims to be further along than it is. + */ +export async function migrate(db: Db, dir = MIGRATIONS_DIR): Promise { + await db.query(` + CREATE TABLE IF NOT EXISTS schema_migrations ( + name text PRIMARY KEY, + applied_at timestamptz NOT NULL DEFAULT now() + ) + `) + + const applied = new Set( + (await db.query<{ name: string }>('SELECT name FROM schema_migrations')).rows.map((r) => r.name), + ) + const files = readdirSync(dir).filter((f) => f.endsWith('.sql')).sort() + const ran: string[] = [] + + for (const file of files) { + if (applied.has(file)) continue + const sql = readFileSync(join(dir, file), 'utf8') + await db.transaction(async (tx) => { + await tx.query(sql) + await tx.query('INSERT INTO schema_migrations (name) VALUES ($1)', [file]) + }) + ran.push(file) + } + return ran +} + +/** CLI entry: `pnpm migrate`. */ +async function main(): Promise { + const { loadConfig } = await import('./config') + const { createPgDb } = await import('./pg') + const config = loadConfig() + const db = createPgDb(config.databaseUrl) + try { + const ran = await migrate(db) + process.stdout.write(ran.length ? `applied: ${ran.join(', ')}\n` : 'already up to date\n') + } finally { + await db.close() + } +} + +if (require.main === module) { + main().catch((err) => { + process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`) + process.exit(1) + }) +} diff --git a/indexer/src/pg.ts b/indexer/src/pg.ts new file mode 100644 index 0000000..b59f829 --- /dev/null +++ b/indexer/src/pg.ts @@ -0,0 +1,43 @@ +import { Pool, type PoolClient } from 'pg' + +import type { Db, QueryResult } from './db' + +/** Wrap a pg Pool (or a checked-out client) in the `Db` interface. */ +function wrap(run: (sql: string, params?: unknown[]) => Promise<{ rows: unknown[]; rowCount: number | null }>): Omit { + return { + async query(sql: string, params: unknown[] = []): Promise> { + const res = await run(sql, params) + return { rows: res.rows as T[], rowCount: res.rowCount ?? 0 } + }, + } +} + +export function createPgDb(connectionString: string): Db { + const pool = new Pool({ connectionString }) + + const clientDb = (client: PoolClient): Db => ({ + ...wrap((sql, params) => client.query(sql, params)), + // Nested transactions are not needed and silently doing nothing would be worse than saying so. + transaction: () => Promise.reject(new Error('nested transactions are not supported')), + close: () => Promise.resolve(), + }) + + return { + ...wrap((sql, params) => pool.query(sql, params)), + async transaction(fn: (tx: Db) => Promise): Promise { + const client = await pool.connect() + try { + await client.query('BEGIN') + const out = await fn(clientDb(client)) + await client.query('COMMIT') + return out + } catch (err) { + await client.query('ROLLBACK').catch(() => {}) + throw err + } finally { + client.release() + } + }, + close: () => pool.end(), + } +} diff --git a/indexer/src/service.ts b/indexer/src/service.ts new file mode 100644 index 0000000..df8dc4a --- /dev/null +++ b/indexer/src/service.ts @@ -0,0 +1,247 @@ +import 'dotenv/config' +import { ethers } from 'ethers' + +import { type Config, loadConfig } from './config' +import { buildAndPublish, latestFeed } from './feed/builder' +import { startHttpServer } from './http/server' +import { scanChainOnce } from './ingest/scanner' +import { refreshSeeds } from './ingest/seeds' +import { IngestStore } from './ingest/store' +import { createLogger } from './logger' +import { createMetrics } from './metrics' +import { migrate } from './migrate' +import { createPgDb } from './pg' +import { refreshVerification } from './verify/refresh' + +import type { LogSource } from './chain/events' + +/** Sleep that resolves early when the abort signal fires, for prompt shutdown. */ +function interruptibleSleep(ms: number, signal: AbortSignal): Promise { + return new Promise((resolve) => { + if (signal.aborted) return resolve() + const timer = setTimeout(done, ms) + function done() { + clearTimeout(timer) + signal.removeEventListener('abort', done) + resolve() + } + signal.addEventListener('abort', done, { once: true }) + }) +} + +/** Adapt an ethers provider to the narrow `LogSource` the scanner needs. */ +function logSource(provider: ethers.providers.JsonRpcProvider): LogSource { + return { + getBlockNumber: () => provider.getBlockNumber(), + getLogs: (filter) => provider.getLogs(filter as ethers.providers.Filter), + getBlock: async (blockNumber) => { + const block = await provider.getBlock(blockNumber) + return block ? { hash: block.hash, parentHash: block.parentHash, timestamp: block.timestamp } : null + }, + } +} + +async function main(): Promise { + const config: Config = loadConfig() + const logger = createLogger(config) + const metrics = createMetrics() + metrics.up.set(1) + + logger.info( + { + chains: config.chains.map((c) => c.key), + trackedTokens: config.trackedTokens.length, + policyVersion: config.policyVersion, + feedSource: config.feedSource, + }, + 'compliance DVN indexer starting', + ) + if (config.trackedTokens.length === 0) { + logger.warn('TRACKED_TOKENS is empty — no transfer edges will be built, so the feed will be empty') + } + + const db = createPgDb(config.databaseUrl) + const ran = await migrate(db) + if (ran.length) logger.info({ migrations: ran }, 'migrations applied') + + const store = new IngestStore(db) + const sources: Record = {} + const providers: Record = {} + for (const chain of config.chains) { + providers[chain.key] = new ethers.providers.JsonRpcProvider(chain.rpc) + sources[chain.key] = logSource(providers[chain.key]) + } + if (config.verifierUrl === '') { + logger.warn('VERIFIER_URL is empty — source-verification lookups disabled, no unverified_contract labels') + } + + // Inbound thresholds come from configuration and are replaced on every boot. + await store.replaceTokenMinimums(config.tokenMinimums) + logger.info( + { minimums: config.tokenMinimums.map((m) => `${m.chain}:${m.token}=${m.minValue}`) }, + 'inbound token thresholds applied', + ) + + // A tracked token with no threshold has its inbound edges recorded but never labelled, which is + // easy to mistake for "no exposure found". Name the gap rather than leaving it silent. + const withMinimum = new Set(config.tokenMinimums.map((m) => m.token)) + const missing = config.trackedTokens.filter((t) => !withMinimum.has(t)) + if (missing.length) { + logger.warn( + { tokens: missing }, + 'tracked tokens have no TOKEN_MINIMUMS entry — their inbound edges will never produce sanctions_1hop_inbound', + ) + } + + // Seeds first: proximity computed against an empty seed set would publish a feed that says + // "nothing is near anything", which a consumer cannot distinguish from a clean graph. + try { + const seeds = await refreshSeeds(store) + metrics.seedCount.set(seeds) + metrics.seedRefreshTotal.inc({ result: 'success' }) + logger.info({ seeds }, 'seed labels loaded') + } catch (err) { + metrics.seedRefreshTotal.inc({ result: 'failure' }) + logger.error({ err: (err as Error).message }, 'initial seed load FAILED — refusing to publish a feed without seeds') + await db.close() + process.exit(1) + } + + let running = true + let scannedOnce = false + const abort = new AbortController() + const shutdown = (signal: string) => { + if (!running) return + logger.warn({ signal }, 'shutting down') + running = false + abort.abort() + } + process.on('SIGINT', () => shutdown('SIGINT')) + process.on('SIGTERM', () => shutdown('SIGTERM')) + + const http = await startHttpServer({ + port: config.httpPort, + metrics, + logger, + feed: () => latestFeed(db), + isReady: () => running && scannedOnce, + db, + statusInfo: () => ({ + chains: config.chains.map((c) => ({ key: c.key, eid: c.eid, chainId: c.chainId, dvn: c.dvn })), + trackedTokens: config.trackedTokens, + tokenMinimums: config.tokenMinimums, + policyVersion: config.policyVersion, + feedSource: config.feedSource, + }), + }) + + let lastFeedAt = 0 + while (running) { + // A new transfer edge is what can change proximity, so the feed is rebuilt on the same tick it + // arrives rather than waiting out the interval. The interval still applies to everything else + // (seed changes, verification results), which no single scan reveals. + let graphChanged = false + + for (const chain of config.chains) { + if (!running) break + try { + const result = await scanChainOnce({ + chain, + source: sources[chain.key], + store, + trackedTokens: config.trackedTokens, + confirmations: config.confirmations, + scanWindow: config.scanWindow, + scanChunk: config.scanChunk, + reorgDepth: config.reorgDepth, + chainByEid: (eid) => config.chains.find((c) => c.eid === eid)?.key, + logger, + }) + metrics.chainHeadBlock.set({ chain: chain.key }, result.head) + metrics.cursorBlock.set({ chain: chain.key }, result.to) + metrics.verdictsIngested.inc({ chain: chain.key }, result.verdicts) + metrics.approvalsIngested.inc({ chain: chain.key }, result.approvals) + metrics.edgesIngested.inc({ chain: chain.key }, result.transfers + result.bridgeSends) + if (result.transfers > 0 || result.bridgeSends > 0) graphChanged = true + if (result.reorgDepth > 0) { + metrics.reorgs.inc({ chain: chain.key }) + metrics.reorgBlocksUnwound.inc({ chain: chain.key }, result.reorgDepth) + } + } catch (err) { + // Per-chain isolation: one chain's RPC failure must not stop the others. + metrics.scanErrors.inc({ chain: chain.key }) + logger.error({ chain: chain.key, err: (err as Error).message }, 'scan failed; will retry next tick') + } + + // Verification is a bounded side quest, not part of ingest: a verifier outage must not stop + // events being collected, so its failures never reach the scan error path. + if (!running || config.verifierUrl === '') continue + try { + const verified = await refreshVerification({ + db, + chain, + reader: { getCode: (address) => providers[chain.key].getCode(address) }, + trackedTokens: config.trackedTokens, + batchSize: config.verifyBatch, + ttlSec: config.verifyTtlSec, + sourcify: { baseUrl: config.verifierUrl }, + logger, + }) + if (verified.inspected) { + metrics.verificationChecked.inc({ chain: chain.key }, verified.inspected) + metrics.verificationUnverified.set({ chain: chain.key }, verified.unverified) + logger.info({ chain: chain.key, ...verified }, 'verification refreshed') + } + } catch (err) { + logger.error({ chain: chain.key, err: (err as Error).message }, 'verification refresh failed') + } + } + scannedOnce = true + + if (running && (graphChanged || Date.now() - lastFeedAt >= config.feedRebuildMs)) { + if (graphChanged) logger.info('new transfer edges — rebuilding the feed immediately') + // Seeds first, on the same cadence as the feed: the proximity labels published below are + // only as fresh as the seed set, and an OFAC update must reach a long-running indexer + // without a restart. A failed refresh keeps the previous seeds — the initial load already + // guaranteed a non-empty set — and the feed still publishes, loudly degraded. + try { + const seeds = await refreshSeeds(store) + metrics.seedCount.set(seeds) + metrics.seedRefreshTotal.inc({ result: 'success' }) + } catch (err) { + metrics.seedRefreshTotal.inc({ result: 'failure' }) + logger.warn({ err: (err as Error).message }, 'seed refresh failed; keeping the previous seed set') + } + try { + const feed = await buildAndPublish(db, { + source: config.feedSource, + policyVersion: config.policyVersion, + ttlSec: config.feedTtlSec, + signingKey: config.feedSigningKey, + }) + lastFeedAt = Date.now() + metrics.feedVersion.set(feed.version) + metrics.feedEntries.set(feed.entries.length) + metrics.feedGeneratedAt.set(feed.generatedAt) + metrics.feedBuildTotal.inc({ result: 'success' }) + logger.info({ version: feed.version, entries: feed.entries.length }, 'feed published') + } catch (err) { + metrics.feedBuildTotal.inc({ result: 'failure' }) + logger.error({ err: (err as Error).message }, 'feed build failed; serving the previous document') + } + } + + if (running) await interruptibleSleep(config.pollMs, abort.signal) + } + + await http.close() + await db.close() + metrics.up.set(0) + logger.info('indexer stopped') +} + +main().catch((err) => { + // eslint-disable-next-line no-console + console.error(err instanceof Error ? err.message : err) + process.exit(1) +}) diff --git a/indexer/src/verify/refresh.ts b/indexer/src/verify/refresh.ts new file mode 100644 index 0000000..35de244 --- /dev/null +++ b/indexer/src/verify/refresh.ts @@ -0,0 +1,182 @@ +import { type SourcifyOptions, lookupMany } from './sourcify' + +import type { Db } from '../db' +import type { Logger } from 'pino' + +/** Just enough of a provider to tell a contract from an EOA. */ +export interface CodeReader { + getCode(address: string): Promise +} + +export interface RefreshVerificationDeps { + db: Db + chain: { key: string; chainId: number } + reader: CodeReader + /** Addresses that are contracts by definition (the configured tokens). */ + trackedTokens: readonly string[] + /** Maximum addresses to resolve per pass, so a cold start does not hammer the verifier. */ + batchSize: number + /** How long a recorded answer is trusted before being re-checked. */ + ttlSec: number + sourcify?: SourcifyOptions + logger: Logger + now?: () => number +} + +export interface RefreshResult { + inspected: number + contracts: number + verified: number + unverified: number + /** Answers we could not obtain; these addresses stay NULL and are retried. */ + unknown: number + rateLimited: boolean +} + +/** + * Resolve verification status for contract addresses the graph has seen. + * + * Two stages, because asking a verifier about an EOA is wasted budget: first establish which + * candidates hold code (one `getCode` each, cached forever after — an address cannot stop being a + * contract), then ask the verifier only about those. + * + * Nothing is written as `verified: false` unless the verifier actually answered. A failed lookup + * leaves the row NULL so it is retried, rather than recording an absence of evidence as evidence. + */ +export async function refreshVerification(deps: RefreshVerificationDeps): Promise { + const now = deps.now ?? Date.now + const nowSec = Math.floor(now() / 1000) + const staleBefore = nowSec - deps.ttlSec + const result: RefreshResult = { + inspected: 0, + contracts: 0, + verified: 0, + unverified: 0, + unknown: 0, + rateLimited: false, + } + + const candidates = await selectCandidates(deps, staleBefore) + if (candidates.length === 0) return result + + // Stage 1: contract or EOA. Recorded either way so an EOA is never re-probed. + const contracts: string[] = [] + for (const address of candidates) { + result.inspected++ + let isContract: boolean + try { + const code = await deps.reader.getCode(address) + isContract = !!code && code !== '0x' + } catch (err) { + deps.logger.debug({ address, err: (err as Error).message }, 'getCode failed; will retry next pass') + continue + } + if (isContract) contracts.push(address) + else await upsertStatus(deps.db, deps.chain.key, address, false, null, nowSec) + } + result.contracts = contracts.length + if (contracts.length === 0) return result + + // Stage 2: ask the verifier. v2 answers per address, so this is one request each — bounded by + // `batchSize` and short-circuited if the verifier starts refusing. + const { statuses, rateLimited } = await lookupMany(contracts, deps.chain.chainId, deps.sourcify) + result.rateLimited = rateLimited + + for (const address of contracts) { + const status = statuses.get(address) + // An unresolved or unknown answer is left unwritten so the row stays NULL and is retried. A + // verifier outage must not turn into a wave of `unverified_contract` labels. + if (status === undefined || status === 'unknown') { + result.unknown++ + continue + } + await upsertStatus(deps.db, deps.chain.key, address, true, status === 'verified', nowSec) + if (status === 'verified') result.verified++ + else result.unverified++ + } + + if (rateLimited) { + deps.logger.warn( + { chain: deps.chain.key, resolved: result.verified + result.unverified, pending: result.unknown }, + 'verifier rate-limited; stopping this pass early and retrying the rest later', + ) + } else if (result.unknown) { + deps.logger.warn( + { chain: deps.chain.key, unknown: result.unknown }, + 'some verification answers unavailable; status left unknown rather than assumed unverified', + ) + } + return result +} + +/** + * Addresses worth resolving: the configured tokens, plus contract-capable participants in the + * graph, oldest-unknown first. + * + * Edge participants are mostly EOAs, so this over-selects — stage 1 filters them out once and + * records the answer, which keeps each address a one-time cost rather than a recurring one. + */ +async function selectCandidates(deps: RefreshVerificationDeps, staleBefore: number): Promise { + const out: string[] = [] + const seen = new Set() + + const fresh = await deps.db.query<{ address: string }>( + 'SELECT address FROM contract_status WHERE chain = $1 AND (verified IS NOT NULL OR is_contract = false) AND checked_at >= $2', + [deps.chain.key, staleBefore], + ) + const settled = new Set(fresh.rows.map((r) => r.address)) + + const push = (address: string) => { + const a = address.toLowerCase() + if (seen.has(a) || settled.has(a) || out.length >= deps.batchSize) return + seen.add(a) + out.push(a) + } + + for (const token of deps.trackedTokens) push(token) + + const participants = await deps.db.query<{ address: string }>( + `SELECT address FROM ( + SELECT from_addr AS address FROM edges WHERE chain = $1 + UNION + SELECT to_addr AS address FROM edges WHERE chain = $1 + ) t + LIMIT $2`, + [deps.chain.key, deps.batchSize * 4], + ) + for (const row of participants.rows) push(row.address) + + return out +} + +async function upsertStatus( + db: Db, + chain: string, + address: string, + isContract: boolean, + verified: boolean | null, + checkedAt: number, +): Promise { + await db.query( + `INSERT INTO contract_status (chain, address, is_contract, verified, checked_at) + VALUES ($1, $2, $3, $4, $5) + ON CONFLICT (chain, address) DO UPDATE + SET is_contract = EXCLUDED.is_contract, + verified = EXCLUDED.verified, + checked_at = EXCLUDED.checked_at`, + [chain, address.toLowerCase(), isContract, verified, checkedAt], + ) +} + +/** + * Addresses to publish `unverified_contract` for. + * + * `verified = false` only — a NULL row means we never got an answer, and publishing on that would + * assert something the verifier never said. + */ +export async function unverifiedContracts(db: Db): Promise { + const res = await db.query<{ address: string }>( + 'SELECT DISTINCT address FROM contract_status WHERE is_contract = true AND verified = false', + ) + return res.rows.map((r) => r.address).sort() +} diff --git a/indexer/src/verify/sourcify.ts b/indexer/src/verify/sourcify.ts new file mode 100644 index 0000000..c74ac6d --- /dev/null +++ b/indexer/src/verify/sourcify.ts @@ -0,0 +1,144 @@ +/** + * Source-verification lookup against Sourcify's v2 API. + * + * v1 (`/check-all-by-addresses`) is in a scheduled brownout and returns 503 telling callers to + * migrate, so this targets `GET /v2/contract/{chainId}/{address}`. v2 answers per address rather + * than in batches, which is why the caller caches aggressively and caps how many it resolves per + * pass — see `refresh.ts`. + * + * The three-way result is the whole point. "Not verified" and "we could not find out" must stay + * distinct: recording the second as the first would let a verifier outage label every contract in + * the graph as unverified. + * + * `baseUrl` is configurable so a self-hosted Sourcify can be used instead of the public instance. + */ + +export type VerificationStatus = 'verified' | 'unverified' | 'unknown' + +export interface HttpResponse { + status: number + body: string +} + +/** Returns the status alongside the body: 404 is a definitive answer, 503 is not. */ +export type Fetcher = (url: string) => Promise + +/** + * Match values that count as "source available". + * + * v2 uses `match` / `exact_match`. The older `perfect` / `partial` are accepted too, since + * `baseUrl` may point at a self-hosted instance on an earlier release. + */ +const VERIFIED_MATCHES = new Set(['match', 'exact_match', 'perfect', 'partial', 'full_match', 'partial_match']) + +export const DEFAULT_SOURCIFY_URL = 'https://sourcify.dev/server' + +const defaultFetch: Fetcher = async (url) => { + const fetch = (await import('node-fetch')).default + const res = await fetch(url) + return { status: res.status, body: await res.text() } +} + +/** + * Read a verification verdict out of a v2 contract response. + * + * `match: null` is an explicit "known to Sourcify, no source match" and means unverified. A body + * this cannot interpret returns `unknown` rather than a guess — biasing toward not labelling, + * since a missed label costs a weak signal while a false one inflates every score it touches. + */ +export function parseMatch(body: string): VerificationStatus { + let parsed: unknown + try { + parsed = JSON.parse(body) + } catch { + return 'unknown' + } + if (parsed === null || typeof parsed !== 'object') return 'unknown' + const obj = parsed as Record + + // `match` is required on a v2 success response, so its absence means this is not one. + if (!('match' in obj) && !('runtimeMatch' in obj) && !('creationMatch' in obj)) return 'unknown' + + for (const key of ['match', 'runtimeMatch', 'creationMatch']) { + const value = obj[key] + if (typeof value === 'string' && VERIFIED_MATCHES.has(value.toLowerCase())) return 'verified' + } + return 'unverified' +} + +export interface SourcifyOptions { + baseUrl?: string + fetcher?: Fetcher + timeoutMs?: number +} + +function withTimeout(p: Promise, ms: number, what: string): Promise { + return new Promise((resolve, reject) => { + const timer = setTimeout(() => reject(new Error(`${what} timed out after ${ms}ms`)), ms) + p.then( + (v) => { + clearTimeout(timer) + resolve(v) + }, + (e) => { + clearTimeout(timer) + reject(e) + }, + ) + }) +} + +/** Thrown when the verifier asks us to back off, so the caller can stop the pass rather than hammer it. */ +export class RateLimited extends Error { + constructor() { + super('verifier rate-limited the request') + this.name = 'RateLimited' + } +} + +/** + * Ask the verifier about one address. + * + * 404 is a real answer — Sourcify has no source for this contract — so it maps to `unverified`. + * Everything else that is not a 200 leaves the status `unknown`. + */ +export async function lookupOne( + address: string, + chainId: number, + opts: SourcifyOptions = {}, +): Promise { + const base = (opts.baseUrl ?? DEFAULT_SOURCIFY_URL).replace(/\/+$/, '') + const fetcher = opts.fetcher ?? defaultFetch + const url = `${base}/v2/contract/${chainId}/${encodeURIComponent(address.toLowerCase())}` + + const res = await withTimeout(fetcher(url), opts.timeoutMs ?? 10_000, `verifier lookup ${address}`) + if (res.status === 429) throw new RateLimited() + if (res.status === 404) return 'unverified' + if (res.status !== 200) return 'unknown' + return parseMatch(res.body) +} + +/** + * Resolve a batch of addresses, one request each. + * + * A rate limit stops the pass immediately and the addresses already resolved are returned — the + * rest stay unrecorded and are retried next time. Pushing through a 429 would only get the + * remaining answers refused anyway. + */ +export async function lookupMany( + addresses: readonly string[], + chainId: number, + opts: SourcifyOptions = {}, +): Promise<{ statuses: Map; rateLimited: boolean }> { + const statuses = new Map() + for (const address of addresses) { + try { + statuses.set(address.toLowerCase(), await lookupOne(address, chainId, opts)) + } catch (err) { + if (err instanceof RateLimited) return { statuses, rateLimited: true } + // A transport failure for one address says nothing about the others; leave it unknown. + statuses.set(address.toLowerCase(), 'unknown') + } + } + return { statuses, rateLimited: false } +} diff --git a/indexer/test/api.spec.ts b/indexer/test/api.spec.ts new file mode 100644 index 0000000..6ec170e --- /dev/null +++ b/indexer/test/api.spec.ts @@ -0,0 +1,131 @@ +import pino from 'pino' +import { afterEach, beforeEach, describe, expect, it } from 'vitest' + +import { type HttpServer, startHttpServer } from '../src/http/server' +import { createMetrics } from '../src/metrics' + +import { applySchema, memDb, seedFixture } from './helpers/memdb' + +const silent = pino({ level: 'silent' }) + +const SANCTIONED = '0x' + 'a'.repeat(40) +const SUBJECT = '0x' + '1'.repeat(40) +const OTHER = '0x' + '2'.repeat(40) +const TOKEN = '0x' + 'd'.repeat(40) +const PAYLOAD = '0x' + 'f'.repeat(64) + +let db: ReturnType +let http: HttpServer | undefined + +beforeEach(() => { + db = memDb() + applySchema(db) +}) + +afterEach(async () => { + await http?.close() + http = undefined +}) + +async function start(): Promise { + http = await startHttpServer({ + port: 0, + metrics: createMetrics(), + logger: silent, + feed: async () => undefined, + isReady: () => true, + db, + statusInfo: () => ({ trackedTokens: [TOKEN], policyVersion: 2 }), + }) + return `http://127.0.0.1:${http.port}` +} + +describe('dashboard API', () => { + it('serves the seed list, filterable by substring', async () => { + await seedFixture(db, { + seeds: [ + { subject: SANCTIONED, label: 'sanctions' }, + { subject: OTHER, label: 'sanctioned_mixer' }, + ], + }) + const base = await start() + + const all = (await (await fetch(`${base}/api/seeds`)).json()) as { seeds: unknown[] } + expect(all.seeds).toHaveLength(2) + + const filtered = (await (await fetch(`${base}/api/seeds?q=${'a'.repeat(6)}`)).json()) as { + seeds: Array<{ subject: string }> + } + expect(filtered.seeds).toEqual([{ subject: SANCTIONED, label: 'sanctions', source: 'ofac' }]) + }) + + it('serves verdicts even when the block row is missing (LEFT JOIN)', async () => { + await db.query( + `INSERT INTO risk_verdicts (chain, block_number, tx_hash, log_index, payload_hash, action, score, reason_mask, evidence_hash) + VALUES ('baseSepolia', 100, '0x01', 0, $1, 3, 100, '9', '0x02')`, + [PAYLOAD], + ) + const base = await start() + + const all = (await (await fetch(`${base}/api/verdicts`)).json()) as { verdicts: Array> } + expect(all.verdicts).toHaveLength(1) + expect(all.verdicts[0]).toMatchObject({ payload_hash: PAYLOAD, action: 3 }) + // numeric arrives as a string from pg and as a number from pg-mem; consumers must String() it. + expect(String(all.verdicts[0].reason_mask)).toBe('9') + + const byHash = (await (await fetch(`${base}/api/verdicts?payloadHash=${PAYLOAD}`)).json()) as { + verdicts: unknown[] + } + expect(byHash.verdicts).toHaveLength(1) + const miss = (await (await fetch(`${base}/api/verdicts?payloadHash=0x${'0'.repeat(64)}`)).json()) as { + verdicts: unknown[] + } + expect(miss.verdicts).toHaveLength(0) + }) + + it('rejects a malformed payloadHash instead of querying with it', async () => { + const base = await start() + expect((await fetch(`${base}/api/verdicts?payloadHash=nope`)).status).toBe(400) + expect((await fetch(`${base}/api/edges?address=nope`)).status).toBe(400) + }) + + it('serves edges filtered by either endpoint', async () => { + await seedFixture(db, { + edges: [ + { token: TOKEN, from: SUBJECT, to: OTHER, value: '5', logIndex: 0 }, + { token: TOKEN, from: OTHER, to: SANCTIONED, value: '7', logIndex: 1 }, + ], + }) + const base = await start() + const bySubject = (await (await fetch(`${base}/api/edges?address=${SUBJECT}`)).json()) as { edges: unknown[] } + expect(bySubject.edges).toHaveLength(1) + const byOther = (await (await fetch(`${base}/api/edges?address=${OTHER}`)).json()) as { edges: unknown[] } + expect(byOther.edges).toHaveLength(2) + }) + + it('serves live proximity labels, not just the published feed', async () => { + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [{ token: TOKEN, from: SUBJECT, to: SANCTIONED, value: '1' }], + }) + const base = await start() + const res = (await (await fetch(`${base}/api/proximity`)).json()) as { + labelled: Array<{ subject: string; labels: string[] }> + } + expect(res.labelled).toEqual([{ subject: SUBJECT, labels: ['sanctions_1hop'] }]) + }) + + it('echoes configuration and counts in /api/status', async () => { + await seedFixture(db, { seeds: [{ subject: SANCTIONED, label: 'sanctions' }] }) + const base = await start() + const status = (await (await fetch(`${base}/api/status`)).json()) as Record + expect(status).toMatchObject({ trackedTokens: [TOKEN], policyVersion: 2, seeds: 1, feed: null }) + }) + + // The demo dashboard reads this API straight from the browser. + it('sends a permissive CORS header on every response', async () => { + const base = await start() + expect((await fetch(`${base}/healthz`)).headers.get('access-control-allow-origin')).toBe('*') + expect((await fetch(`${base}/api/seeds`)).headers.get('access-control-allow-origin')).toBe('*') + }) +}) diff --git a/indexer/test/config.spec.ts b/indexer/test/config.spec.ts new file mode 100644 index 0000000..2ec2f30 --- /dev/null +++ b/indexer/test/config.spec.ts @@ -0,0 +1,201 @@ +import { describe, expect, it } from 'vitest' + +import { CHAIN_REGISTRY, loadConfig } from '../src/config' +import { MIXER_ADDRESSES, parseOfacList, parseOpenSanctionsNdjson, refreshSeeds } from '../src/ingest/seeds' +import { IngestStore } from '../src/ingest/store' + +import { applySchema, memDb } from './helpers/memdb' + +const KEY = '0x' + '7'.repeat(64) +const ADDR = '0x' + 'a'.repeat(40) + +function baseEnv(overrides: Record = {}): Record { + return { + DATABASE_URL: 'postgres://indexer:indexer@localhost:5432/indexer', + FEED_SIGNING_KEY: KEY, + DVN_BASE_SEPOLIA: ADDR, + DVN_OPTIMISM_SEPOLIA: ADDR, + ...overrides, + } +} + +describe('loadConfig', () => { + it('loads a valid config with defaults applied', () => { + const cfg = loadConfig(baseEnv()) + expect(cfg.pollMs).toBe(15000) + expect(cfg.confirmations).toBe(5) + expect(cfg.reorgDepth).toBe(32) + expect(cfg.feedSource).toBe('trusted-indexer-a') + expect(cfg.feedTtlSec).toBe(7200) + expect(cfg.policyVersion).toBe(2) // v2: 3-hop graph proximity + expect(cfg.httpPort).toBe(9091) + expect(cfg.chains.map((c) => c.key).sort()).toEqual(Object.keys(CHAIN_REGISTRY).sort()) + }) + + it('accepts a bare 64-hex signing key and normalizes it to the 0x form', () => { + // ethers accepts either notation, so rejecting the bare form would block a working config. + const bare = '7'.repeat(64) + expect(loadConfig(baseEnv({ FEED_SIGNING_KEY: bare })).feedSigningKey).toBe(`0x${bare}`) + }) + + it('requires a database url and a signing key', () => { + expect(() => loadConfig(baseEnv({ DATABASE_URL: undefined }))).toThrowError(/DATABASE_URL/) + expect(() => loadConfig(baseEnv({ FEED_SIGNING_KEY: undefined }))).toThrowError(/FEED_SIGNING_KEY/) + expect(() => loadConfig(baseEnv({ FEED_SIGNING_KEY: '0xnope' }))).toThrowError(/FEED_SIGNING_KEY/) + }) + + it('requires a DVN address for every enabled chain', () => { + expect(() => loadConfig(baseEnv({ DVN_BASE_SEPOLIA: undefined }))).toThrowError(/DVN_BASE_SEPOLIA/) + const cfg = loadConfig(baseEnv({ CHAINS_ENABLED: 'optimismSepolia', DVN_BASE_SEPOLIA: undefined })) + expect(cfg.chains.map((c) => c.key)).toEqual(['optimismSepolia']) + }) + + it('rejects an unknown chain key', () => { + expect(() => loadConfig(baseEnv({ CHAINS_ENABLED: 'ethereum' }))).toThrowError(/unknown chain/) + }) + + it('parses and lowercases tracked tokens', () => { + const cfg = loadConfig(baseEnv({ TRACKED_TOKENS: `${'0x' + 'A'.repeat(40)}, ${'0x' + 'b'.repeat(40)}` })) + expect(cfg.trackedTokens).toEqual(['0x' + 'a'.repeat(40), '0x' + 'b'.repeat(40)]) + }) + + it('rejects a malformed tracked token', () => { + expect(() => loadConfig(baseEnv({ TRACKED_TOKENS: '0xnope' }))).toThrowError(/TRACKED_TOKENS/) + }) + + // A document that can expire before its replacement is built would make the worker's screening + // flap between having feed labels and not. + it('rejects a TTL that does not exceed the rebuild interval', () => { + expect(() => loadConfig(baseEnv({ FEED_TTL_SEC: '60', FEED_REBUILD_MS: '600000' }))).toThrowError( + /FEED_TTL_SEC/, + ) + }) + + it('defaults to the public Sourcify instance', () => { + const cfg = loadConfig(baseEnv()) + expect(cfg.verifierUrl).toBe('https://sourcify.dev/server') + expect(cfg.verifyBatch).toBe(50) + expect(cfg.verifyTtlSec).toBe(604_800) + }) + + it('allows a self-hosted verifier and an explicit opt-out', () => { + expect(loadConfig(baseEnv({ VERIFIER_URL: 'https://my-sourcify.internal/server' })).verifierUrl).toBe( + 'https://my-sourcify.internal/server', + ) + // Empty disables verification rather than falling back to the public instance. + expect(loadConfig(baseEnv({ VERIFIER_URL: '' })).verifierUrl).toBe('') + }) + + it('exposes the EVM chain id the verifier keys on, distinct from the LayerZero eid', () => { + const base = loadConfig(baseEnv()).chains.find((c) => c.key === 'baseSepolia')! + expect(base.eid).toBe(40245) + expect(base.chainId).toBe(84532) + }) + + it('has no token minimums by default', () => { + expect(loadConfig(baseEnv()).tokenMinimums).toEqual([]) + }) + + it('parses chain:token:minValue triples and lowercases the token', () => { + const cfg = loadConfig( + baseEnv({ + TOKEN_MINIMUMS: `baseSepolia:${'0x' + 'A'.repeat(40)}:10000000000000000, optimismSepolia:${'0x' + 'b'.repeat(40)}:10000`, + }), + ) + expect(cfg.tokenMinimums).toEqual([ + { chain: 'baseSepolia', token: '0x' + 'a'.repeat(40), minValue: '10000000000000000' }, + { chain: 'optimismSepolia', token: '0x' + 'b'.repeat(40), minValue: '10000' }, + ]) + }) + + // A uint256 threshold does not fit a JS number, so it stays a decimal string end to end. + it('keeps a uint256-scale minimum exact', () => { + const huge = '115792089237316195423570985008687907853269984665640564039457584007913129639935' + const cfg = loadConfig(baseEnv({ TOKEN_MINIMUMS: `baseSepolia:${'0x' + 'a'.repeat(40)}:${huge}` })) + expect(cfg.tokenMinimums[0].minValue).toBe(huge) + }) + + it('rejects malformed token minimums', () => { + const bad = (v: string) => () => loadConfig(baseEnv({ TOKEN_MINIMUMS: v })) + expect(bad(`baseSepolia:${'0x' + 'a'.repeat(40)}`)).toThrowError(/chain:token:minValue/) + expect(bad(`ethereum:${'0x' + 'a'.repeat(40)}:1`)).toThrowError(/unknown chain/) + expect(bad('baseSepolia:0xnope:1')).toThrowError(/not a 20-byte EVM address/) + expect(bad(`baseSepolia:${'0x' + 'a'.repeat(40)}:0.01`)).toThrowError(/decimal integer/) + expect(bad(`baseSepolia:${'0x' + 'a'.repeat(40)}:1e16`)).toThrowError(/decimal integer/) + }) + + it('rejects a duplicate chain/token pair rather than silently picking one', () => { + const t = '0x' + 'a'.repeat(40) + expect(() => loadConfig(baseEnv({ TOKEN_MINIMUMS: `baseSepolia:${t}:1,baseSepolia:${t}:2` }))).toThrowError( + /duplicate entry/, + ) + }) + + it('aggregates every problem into one error', () => { + try { + loadConfig({ TRACKED_TOKENS: '0xnope' }) + expect.fail('expected a config error') + } catch (err) { + const message = (err as Error).message + expect(message).toContain('DATABASE_URL') + expect(message).toContain('FEED_SIGNING_KEY') + expect(message).toContain('DVN_BASE_SEPOLIA') + expect(message).toContain('TRACKED_TOKENS') + } + }) +}) + +describe('seed parsing', () => { + it('parses the OFAC address list', () => { + expect(parseOfacList(JSON.stringify(['0xAAA', '0xbbb', 'nothex', '']))).toEqual(['0xaaa', '0xbbb']) + }) + + it('extracts EVM publicKeys from OpenSanctions CryptoWallet entities', () => { + const ndjson = [ + JSON.stringify({ schema: 'CryptoWallet', properties: { publicKey: ['0x' + '1'.repeat(40)] } }), + JSON.stringify({ schema: 'Person', properties: { name: ['Bob'] } }), + JSON.stringify({ schema: 'CryptoWallet', properties: { publicKey: ['bc1qxyz'] } }), + 'not json', + '', + ].join('\n') + expect(parseOpenSanctionsNdjson(ndjson)).toEqual(['0x' + '1'.repeat(40)]) + }) +}) + +describe('refreshSeeds', () => { + it('loads sanctions and mixer seeds', async () => { + const db = memDb() + applySchema(db) + const store = new IngestStore(db) + const ofac = '0x' + '1'.repeat(40) + const os = '0x' + '2'.repeat(40) + + const n = await refreshSeeds(store, async (url) => + url.includes('opensanctions') + ? JSON.stringify({ schema: 'CryptoWallet', properties: { publicKey: [os] } }) + : JSON.stringify([ofac]), + ) + expect(n).toBe(2 + MIXER_ADDRESSES.length) + + const rows = await db.query<{ subject: string; label: string }>('SELECT subject, label FROM seed_labels') + const labels = new Map(rows.rows.map((r) => [r.subject, r.label])) + expect(labels.get(ofac)).toBe('sanctions') + expect(labels.get(os)).toBe('sanctions') + expect(labels.get(MIXER_ADDRESSES[0])).toBe('sanctioned_mixer') + }) + + // A sanction lifted upstream must stop seeding proximity, so seeds are replaced not merged. + it('replaces a source rather than merging into it', async () => { + const db = memDb() + applySchema(db) + const store = new IngestStore(db) + const first = '0x' + '1'.repeat(40) + const second = '0x' + '2'.repeat(40) + + await refreshSeeds(store, async (url) => (url.includes('opensanctions') ? '' : JSON.stringify([first]))) + await refreshSeeds(store, async (url) => (url.includes('opensanctions') ? '' : JSON.stringify([second]))) + + const rows = await db.query<{ subject: string }>("SELECT subject FROM seed_labels WHERE label = 'sanctions'") + expect(rows.rows.map((r) => r.subject)).toEqual([second]) + }) +}) diff --git a/indexer/test/feed.spec.ts b/indexer/test/feed.spec.ts new file mode 100644 index 0000000..dd80f63 --- /dev/null +++ b/indexer/test/feed.spec.ts @@ -0,0 +1,170 @@ +import { ethers } from 'ethers' +import { beforeEach, describe, expect, it } from 'vitest' + +import { type FeedDocument, buildAndPublish, latestFeed, nextVersion, signFeed } from '../src/feed/builder' +import { canonicalize } from '../src/feed/canonical' + +import { applySchema, memDb, seedFixture } from './helpers/memdb' + +const KEY = '0x' + '7'.repeat(64) +const SIGNER = new ethers.Wallet(KEY) +const SANCTIONED = '0x' + 'a'.repeat(40) +const SUBJECT = '0x' + '1'.repeat(40) +const TOKEN = '0x' + 'd'.repeat(40) +const NOW_MS = 1_800_000_000_000 +const NOW_SEC = Math.floor(NOW_MS / 1000) + +let db: ReturnType + +beforeEach(() => { + db = memDb() + applySchema(db) +}) + +const opts = { + source: 'trusted-indexer-a', + policyVersion: 1, + ttlSec: 7200, + signingKey: KEY, + now: () => NOW_MS, +} + +describe('canonicalize', () => { + /** + * This fixture is the contract with the worker. `worker/test/feed.spec.ts` exercises the same + * rules against the same shape; if either side's implementation drifts, one of the two fails. + */ + it('produces the agreed byte sequence for a representative document', () => { + const doc = { + version: 128, + generatedAt: 1782090000, + expiresAt: 1782093600, + source: 'trusted-indexer-a', + policyVersion: 1, + entries: [{ address: '0xabc', labels: ['sanctions_1hop', 'mixer_exposure'] }], + } + expect(canonicalize(doc)).toBe( + '{"entries":[{"address":"0xabc","labels":["sanctions_1hop","mixer_exposure"]}],' + + '"expiresAt":1782093600,"generatedAt":1782090000,"policyVersion":1,' + + '"source":"trusted-indexer-a","version":128}', + ) + }) + + it('sorts keys and preserves array order', () => { + expect(canonicalize({ b: 1, a: 2 })).toBe('{"a":2,"b":1}') + expect(canonicalize([3, 1, 2])).toBe('[3,1,2]') + }) + + it('rejects non-integer numbers, which cannot round-trip across languages', () => { + expect(() => canonicalize({ confidence: 0.8 })).toThrow(/non-integer/) + }) +}) + +describe('signFeed', () => { + it('signs the canonical form excluding the signature field', async () => { + const doc: FeedDocument = { + version: 1, + generatedAt: NOW_SEC, + expiresAt: NOW_SEC + 3600, + source: 'trusted-indexer-a', + policyVersion: 1, + entries: [{ address: SUBJECT, labels: ['sanctions_1hop'] }], + } + const signed = await signFeed(doc, KEY) + const { signature, ...rest } = signed + expect(ethers.utils.verifyMessage(canonicalize(rest), signature)).toBe(SIGNER.address) + }) + + it('produces a signature that fails once the document is altered', async () => { + const doc: FeedDocument = { + version: 1, + generatedAt: NOW_SEC, + expiresAt: NOW_SEC + 3600, + source: 'trusted-indexer-a', + policyVersion: 1, + entries: [{ address: SUBJECT, labels: ['sanctions_1hop'] }], + } + const { signature } = await signFeed(doc, KEY) + const tampered = { ...doc, entries: [{ address: SUBJECT, labels: ['sanctions'] }] } + expect(ethers.utils.verifyMessage(canonicalize(tampered), signature)).not.toBe(SIGNER.address) + }) +}) + +describe('buildAndPublish', () => { + it('publishes the one-hop labels as feed entries', async () => { + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [{ token: TOKEN, from: SUBJECT, to: SANCTIONED, value: '1' }], + }) + const feed = await buildAndPublish(db, opts) + expect(feed.version).toBe(1) + expect(feed.generatedAt).toBe(NOW_SEC) + expect(feed.expiresAt).toBe(NOW_SEC + 7200) + expect(feed.policyVersion).toBe(1) + expect(feed.entries).toEqual([{ address: SUBJECT, labels: ['sanctions_1hop'] }]) + }) + + // Sanctions and mixer lists are NOT republished: the worker reads them first-hand, and + // re-feeding one as a trusted_indexer claim would launder an authoritative source into a + // derived one — which also lowers its enforcement authority. + it('does not republish the seed labels themselves', async () => { + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [{ token: TOKEN, from: SUBJECT, to: SANCTIONED, value: '1' }], + }) + const feed = await buildAndPublish(db, opts) + expect(feed.entries.map((e) => e.address)).not.toContain(SANCTIONED) + expect(feed.entries.flatMap((e) => e.labels)).not.toContain('sanctions') + }) + + // action / confidence / score are all the worker's to decide. Asserting them here would move + // enforcement authority to the indexer. + it('emits no action, confidence, or score', async () => { + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [{ token: TOKEN, from: SUBJECT, to: SANCTIONED, value: '1' }], + }) + const feed = await buildAndPublish(db, opts) + const entry = feed.entries[0] as unknown as Record + expect(Object.keys(entry).sort()).toEqual(['address', 'labels']) + }) + + it('increments the version on every publish', async () => { + expect(await nextVersion(db)).toBe(1) + await buildAndPublish(db, opts) + expect(await nextVersion(db)).toBe(2) + const second = await buildAndPublish(db, opts) + expect(second.version).toBe(2) + }) + + // The worker refuses a version it has already accepted, so a repeat after a restart would be + // rejected rather than applied. Persisting the counter is what prevents that. + it('resumes the version counter from the database, not from memory', async () => { + await buildAndPublish(db, opts) + await buildAndPublish(db, opts) + // A fresh builder over the same database must not reissue version 1. + expect(await nextVersion(db)).toBe(3) + }) + + it('publishes an empty feed rather than failing when the graph is empty', async () => { + const feed = await buildAndPublish(db, opts) + expect(feed.entries).toEqual([]) + expect(feed.signature).toMatch(/^0x[0-9a-f]{130}$/) + }) + + it('stores the signed document so it can be served after a restart', async () => { + const published = await buildAndPublish(db, opts) + const served = await latestFeed(db) + expect(served).toEqual(published) + }) + + it('serves the newest version when several exist', async () => { + await buildAndPublish(db, opts) + const newest = await buildAndPublish(db, opts) + expect((await latestFeed(db))!.version).toBe(newest.version) + }) + + it('has no feed before the first build', async () => { + expect(await latestFeed(db)).toBeUndefined() + }) +}) diff --git a/indexer/test/helpers/memdb.ts b/indexer/test/helpers/memdb.ts new file mode 100644 index 0000000..a050148 --- /dev/null +++ b/indexer/test/helpers/memdb.ts @@ -0,0 +1,119 @@ +import { readFileSync, readdirSync } from 'fs' +import { join } from 'path' + +import { newDb } from 'pg-mem' + +import type { Db, QueryResult } from '../../src/db' + +const MIGRATIONS_DIR = join(__dirname, '..', '..', 'db', 'migrations') + +/** + * An in-memory Postgres for tests, so the SQL is genuinely executed rather than mocked. + * + * pg-mem is not a complete Postgres, but it covers the joins, `ON CONFLICT`, and numeric + * comparisons this schema relies on — which is the part worth testing. Anything it cannot run + * would fail loudly here rather than in production. + */ +export function memDb(): Db & { raw: ReturnType } { + const mem = newDb() + const backend = mem.public + + const db: Db & { raw: ReturnType } = { + raw: mem, + async query(sql: string, params: unknown[] = []): Promise> { + const rows = params.length ? backend.many(substitute(sql, params)) : runMaybe(backend, sql) + const list = Array.isArray(rows) ? rows : [] + return { rows: list as T[], rowCount: list.length } + }, + // pg-mem has no real transaction isolation for our purposes; running inline is enough to + // exercise the statements, and the atomicity itself is a Postgres guarantee, not our logic. + transaction: async (fn: (tx: Db) => Promise): Promise => fn(db), + close: async () => {}, + } + return db +} + +/** Statements that legitimately return nothing (DDL, DELETE, INSERT) must not throw on `many`. */ +function runMaybe(backend: ReturnType['public'], sql: string): unknown[] { + const result = backend.query(sql) + return result.rows ?? [] +} + +/** + * Inline `$n` parameters, because pg-mem's public API takes plain SQL. + * + * Test-only: values are quoted defensively, but nothing here ever sees untrusted input. + */ +function substitute(sql: string, params: unknown[]): string { + return sql.replace(/\$(\d+)/g, (_m, idx: string) => literal(params[Number(idx) - 1])) +} + +function literal(v: unknown): string { + if (v === null || v === undefined) return 'NULL' + if (typeof v === 'number') return String(v) + if (typeof v === 'boolean') return v ? 'TRUE' : 'FALSE' + return `'${String(v).replace(/'/g, "''")}'` +} + +/** Apply every shipped migration in order, so tests run against the real schema. */ +export function applySchema(db: Db & { raw: ReturnType }): void { + for (const file of readdirSync(MIGRATIONS_DIR).filter((f) => f.endsWith('.sql')).sort()) { + db.raw.public.none(readFileSync(join(MIGRATIONS_DIR, file), 'utf8')) + } +} + +export async function seedFixture( + db: Db, + rows: { + seeds?: Array<{ subject: string; label: string; source?: string }> + edges?: Array<{ + chain?: string + block?: number + tx?: string + logIndex?: number + token: string + from: string + to: string + value: string + /** 'bridge' for a cross-chain send, whose `to` lives on `dstChain`. */ + kind?: string + dstChain?: string + }> + minimums?: Array<{ chain?: string; token: string; min: string }> + }, +): Promise { + for (const s of rows.seeds ?? []) { + await db.query('INSERT INTO seed_labels (subject, label, source) VALUES ($1, $2, $3)', [ + s.subject.toLowerCase(), + s.label, + s.source ?? 'ofac', + ]) + } + let i = 0 + for (const e of rows.edges ?? []) { + i++ + await db.query( + `INSERT INTO edges (chain, block_number, tx_hash, log_index, token, from_addr, to_addr, value, kind, dst_chain) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, + [ + e.chain ?? 'baseSepolia', + e.block ?? 100, + e.tx ?? `0x${String(i).padStart(64, '0')}`, + e.logIndex ?? 0, + e.token.toLowerCase(), + e.from.toLowerCase(), + e.to.toLowerCase(), + e.value, + e.kind ?? 'transfer', + e.dstChain ?? null, + ], + ) + } + for (const m of rows.minimums ?? []) { + await db.query('INSERT INTO token_minimums (chain, token, min_value) VALUES ($1, $2, $3)', [ + m.chain ?? 'baseSepolia', + m.token.toLowerCase(), + m.min, + ]) + } +} diff --git a/indexer/test/proximity.spec.ts b/indexer/test/proximity.spec.ts new file mode 100644 index 0000000..3d40a77 --- /dev/null +++ b/indexer/test/proximity.spec.ts @@ -0,0 +1,534 @@ +import { beforeEach, describe, expect, it } from 'vitest' + +import { GRAPH_DEPTH, computeProximity, exposureCounts, inboundLabels, outboundLabels } from '../src/graph/proximity' +import { IngestStore } from '../src/ingest/store' + +import { applySchema, memDb, seedFixture } from './helpers/memdb' + + +const SANCTIONED = '0x' + 'a'.repeat(40) +const MIXER = '0x' + 'b'.repeat(40) +const SUBJECT = '0x' + '1'.repeat(40) +const OTHER = '0x' + '2'.repeat(40) +const THIRD = '0x' + '3'.repeat(40) +const FOURTH = '0x' + '4'.repeat(40) +const TOKEN = '0x' + 'd'.repeat(40) +const UNTRACKED_TOKEN = '0x' + 'e'.repeat(40) + +// 0.01 ETH in wei — the inbound threshold from the worker's N_HOP policy. +const MIN = '10000000000000000' + +let db: ReturnType + +beforeEach(() => { + db = memDb() + applySchema(db) +}) + +describe('graph depth', () => { + // Depth is a policy decision, not an implementation detail. Pinning it here means raising it + // requires deliberately changing this expectation. + it('is fixed at 3, matching the worker N_HOP policy', () => { + expect(GRAPH_DEPTH).toBe(3) + }) + + // Every edge past the subject's own first hop must clear the token minimum, so with no + // minimums configured the graph degrades to depth 1 — exactly the pre-v2 behaviour. + it('does not extend past one hop when no token minimums are configured', async () => { + // SUBJECT -> OTHER -> SANCTIONED. Only OTHER is one hop away. + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [ + { token: TOKEN, from: SUBJECT, to: OTHER, value: '1', logIndex: 0 }, + { token: TOKEN, from: OTHER, to: SANCTIONED, value: '1', logIndex: 1 }, + ], + }) + expect(await computeProximity(db)).toEqual([{ subject: OTHER, labels: ['sanctions_1hop'] }]) + }) +}) + +describe('multi-hop paths (depth 2-3)', () => { + it('labels a 2-hop route at sanctions_2hop and the intermediary at 1 hop', async () => { + // SUBJECT -> OTHER -> SANCTIONED, the relayed edge above the minimum. + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [ + { token: TOKEN, from: SUBJECT, to: OTHER, value: '1', block: 100, logIndex: 0 }, + { token: TOKEN, from: OTHER, to: SANCTIONED, value: MIN, block: 101, logIndex: 1 }, + ], + minimums: [{ token: TOKEN, min: MIN }], + }) + expect(await computeProximity(db)).toEqual([ + { subject: SUBJECT, labels: ['sanctions_2hop'] }, + { subject: OTHER, labels: ['sanctions_1hop'] }, + ]) + }) + + it('labels a 3-hop route at sanctions_3hop, and stops at GRAPH_DEPTH', async () => { + // FOURTH -> SUBJECT -> OTHER -> THIRD -> SANCTIONED: SUBJECT is 3 hops out, FOURTH is 4. + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [ + { token: TOKEN, from: FOURTH, to: SUBJECT, value: MIN, block: 99, logIndex: 0 }, + { token: TOKEN, from: SUBJECT, to: OTHER, value: '1', block: 100, logIndex: 1 }, + { token: TOKEN, from: OTHER, to: THIRD, value: MIN, block: 101, logIndex: 2 }, + { token: TOKEN, from: THIRD, to: SANCTIONED, value: MIN, block: 102, logIndex: 3 }, + ], + minimums: [{ token: TOKEN, min: MIN }], + }) + const labelled = await outboundLabels(db) + expect(labelled).toContainEqual({ subject: SUBJECT, label: 'sanctions_3hop' }) + expect(labelled.find((l) => l.subject === FOURTH)).toBeUndefined() // 4 hops: out of reach + }) + + it('reports the closest approach only, not every longer route', async () => { + // A direct edge AND a 2-hop route to the same seed class -> just sanctions_1hop. + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [ + { token: TOKEN, from: SUBJECT, to: SANCTIONED, value: '1', block: 100, logIndex: 0 }, + { token: TOKEN, from: SUBJECT, to: OTHER, value: '1', block: 100, logIndex: 1 }, + { token: TOKEN, from: OTHER, to: SANCTIONED, value: MIN, block: 101, logIndex: 2 }, + ], + minimums: [{ token: TOKEN, min: MIN }], + }) + expect(await outboundLabels(db)).toContainEqual({ subject: SUBJECT, label: 'sanctions_1hop' }) + expect(await outboundLabels(db)).not.toContainEqual({ subject: SUBJECT, label: 'sanctions_2hop' }) + }) + + // The smear defence: someone the subject once paid later dusts a sanctioned address. The + // dust edge is not the subject's act, so below the minimum it cannot extend a path to them. + it('does not extend a path over a relayed edge below the minimum', async () => { + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [ + { token: TOKEN, from: SUBJECT, to: OTHER, value: MIN, block: 100, logIndex: 0 }, + { token: TOKEN, from: OTHER, to: SANCTIONED, value: '1', block: 101, logIndex: 1 }, // dust + ], + minimums: [{ token: TOKEN, min: MIN }], + }) + expect((await outboundLabels(db)).find((l) => l.subject === SUBJECT)).toBeUndefined() + // The dusting intermediary still earns its own 1-hop label — sending was its act. + expect(await outboundLabels(db)).toContainEqual({ subject: OTHER, label: 'sanctions_1hop' }) + }) + + // Funds cannot flow backwards in time: the relayed edge predates the subject's own. + it('rejects a path whose hops go backwards in block order', async () => { + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [ + { token: TOKEN, from: OTHER, to: SANCTIONED, value: MIN, block: 100, logIndex: 0 }, + { token: TOKEN, from: SUBJECT, to: OTHER, value: '1', block: 200, logIndex: 1 }, // later + ], + minimums: [{ token: TOKEN, min: MIN }], + }) + expect((await outboundLabels(db)).find((l) => l.subject === SUBJECT)).toBeUndefined() + }) + + it('does not chain hops across different chains', async () => { + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [ + { chain: 'baseSepolia', token: TOKEN, from: SUBJECT, to: OTHER, value: '1', block: 100, logIndex: 0 }, + { chain: 'optimismSepolia', token: TOKEN, from: OTHER, to: SANCTIONED, value: MIN, block: 200, logIndex: 1 }, + ], + minimums: [ + { chain: 'baseSepolia', token: TOKEN, min: MIN }, + { chain: 'optimismSepolia', token: TOKEN, min: MIN }, + ], + }) + expect((await outboundLabels(db)).find((l) => l.subject === SUBJECT)).toBeUndefined() + }) + + // A route through another sanctioned address is that address's proximity, not a longer path. + it('does not route a path through a seed', async () => { + await seedFixture(db, { + seeds: [ + { subject: SANCTIONED, label: 'sanctions' }, + { subject: MIXER, label: 'sanctioned_mixer' }, + ], + edges: [ + { token: TOKEN, from: SUBJECT, to: MIXER, value: '1', block: 100, logIndex: 0 }, + { token: TOKEN, from: MIXER, to: SANCTIONED, value: MIN, block: 101, logIndex: 1 }, + ], + minimums: [{ token: TOKEN, min: MIN }], + }) + // Direct mixer exposure, but no sanctions_2hop "through" the mixer. + expect(await outboundLabels(db)).toEqual([{ subject: SUBJECT, label: 'mixer_exposure' }]) + }) + + it('grades mixer proximity by depth too', async () => { + await seedFixture(db, { + seeds: [{ subject: MIXER, label: 'sanctioned_mixer' }], + edges: [ + { token: TOKEN, from: SUBJECT, to: OTHER, value: '1', block: 100, logIndex: 0 }, + { token: TOKEN, from: OTHER, to: MIXER, value: MIN, block: 101, logIndex: 1 }, + ], + minimums: [{ token: TOKEN, min: MIN }], + }) + expect(await outboundLabels(db)).toContainEqual({ subject: SUBJECT, label: 'mixer_exposure_2hop' }) + }) + + it('labels inbound multi-hop receipt when every edge clears the minimum', async () => { + // SANCTIONED -> OTHER -> SUBJECT, both edges above the minimum. + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [ + { token: TOKEN, from: SANCTIONED, to: OTHER, value: MIN, block: 100, logIndex: 0 }, + { token: TOKEN, from: OTHER, to: SUBJECT, value: MIN, block: 101, logIndex: 1 }, + ], + minimums: [{ token: TOKEN, min: MIN }], + }) + const labelled = await inboundLabels(db) + expect(labelled).toContainEqual({ subject: SUBJECT, label: 'sanctions_2hop_inbound' }) + expect(labelled).toContainEqual({ subject: OTHER, label: 'sanctions_1hop_inbound' }) + }) + + /** + * Mints and burns are Transfer events to/from the zero address, and an OFT cross-chain send is + * exactly that pair. Left as a vertex it joins every holder to every other one: here B burned + * (cross-chain send) and the token later minted to OTHER, who paid a sanctioned address — two + * unrelated supply events that must not put B two hops from a seed. + */ + it('never routes a path through the zero address (mint/burn)', async () => { + const ZERO = '0x' + '0'.repeat(40) + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [ + { token: TOKEN, from: SUBJECT, to: ZERO, value: MIN, block: 100, logIndex: 0 }, // burn + { token: TOKEN, from: ZERO, to: OTHER, value: MIN, block: 101, logIndex: 1 }, // mint + { token: TOKEN, from: OTHER, to: SANCTIONED, value: MIN, block: 102, logIndex: 2 }, + ], + minimums: [{ token: TOKEN, min: MIN }], + }) + const labelled = await outboundLabels(db) + expect(labelled).toContainEqual({ subject: OTHER, label: 'sanctions_1hop' }) + expect(labelled.find((l) => l.subject === SUBJECT)).toBeUndefined() // no 3-hop via 0x0 + expect(labelled.find((l) => l.subject === ZERO)).toBeUndefined() // 0x0 is not an actor + }) + + it('does not label inbound receipt when the final edge is dust', async () => { + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [ + { token: TOKEN, from: SANCTIONED, to: OTHER, value: MIN, block: 100, logIndex: 0 }, + { token: TOKEN, from: OTHER, to: SUBJECT, value: '1', block: 101, logIndex: 1 }, // dust + ], + minimums: [{ token: TOKEN, min: MIN }], + }) + expect((await inboundLabels(db)).find((l) => l.subject === SUBJECT)).toBeUndefined() + }) +}) + +describe('outbound labels (subject -> seed)', () => { + it('labels a subject that sent to a sanctioned address, at any value', async () => { + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [{ token: TOKEN, from: SUBJECT, to: SANCTIONED, value: '1' }], // 1 wei is enough + }) + expect(await outboundLabels(db)).toEqual([{ subject: SUBJECT, label: 'sanctions_1hop' }]) + }) + + it('needs no token minimum configured — sending is the subject own act', async () => { + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [{ token: UNTRACKED_TOKEN, from: SUBJECT, to: SANCTIONED, value: '1' }], + }) + expect((await outboundLabels(db)).length).toBe(1) + }) + + it('maps a sanctioned mixer to mixer_exposure', async () => { + await seedFixture(db, { + seeds: [{ subject: MIXER, label: 'sanctioned_mixer' }], + edges: [{ token: TOKEN, from: SUBJECT, to: MIXER, value: '1' }], + }) + expect(await outboundLabels(db)).toEqual([{ subject: SUBJECT, label: 'mixer_exposure' }]) + }) + + it('does not label the seed itself when it moves funds', async () => { + await seedFixture(db, { + seeds: [ + { subject: SANCTIONED, label: 'sanctions' }, + { subject: MIXER, label: 'sanctioned_mixer' }, + ], + edges: [{ token: TOKEN, from: SANCTIONED, to: MIXER, value: '1' }], + }) + expect(await outboundLabels(db)).toEqual([]) + }) + + it('ignores a self-transfer', async () => { + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [{ token: TOKEN, from: SANCTIONED, to: SANCTIONED, value: '1' }], + }) + expect(await outboundLabels(db)).toEqual([]) + }) + + it('leaves an unrelated subject unlabelled', async () => { + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [{ token: TOKEN, from: SUBJECT, to: OTHER, value: '1' }], + }) + expect(await outboundLabels(db)).toEqual([]) + }) +}) + +describe('inbound labels (seed -> subject)', () => { + it('labels a subject that received at or above the minimum', async () => { + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [{ token: TOKEN, from: SANCTIONED, to: SUBJECT, value: MIN }], + minimums: [{ token: TOKEN, min: MIN }], + }) + expect(await inboundLabels(db)).toEqual([{ subject: SUBJECT, label: 'sanctions_1hop_inbound' }]) + }) + + // The dusting defence: a sanctioned address paying a victim 1 wei must not taint them. + it('IGNORES a dust transfer below the minimum', async () => { + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [{ token: TOKEN, from: SANCTIONED, to: SUBJECT, value: '1' }], + minimums: [{ token: TOKEN, min: MIN }], + }) + expect(await inboundLabels(db)).toEqual([]) + }) + + // No configured minimum means we cannot judge the amount, so we do not label at all. A + // permissive default here would turn every dust transfer into evidence. + it('does not label a token with no configured minimum', async () => { + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [{ token: UNTRACKED_TOKEN, from: SANCTIONED, to: SUBJECT, value: MIN }], + }) + expect(await inboundLabels(db)).toEqual([]) + }) + + it('compares uint256 values numerically, not as strings', async () => { + // '9' > '1000...' lexicographically but is far smaller numerically. A text comparison would + // wrongly label this. + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [{ token: TOKEN, from: SANCTIONED, to: SUBJECT, value: '9' }], + minimums: [{ token: TOKEN, min: MIN }], + }) + expect(await inboundLabels(db)).toEqual([]) + }) + + it('handles a value larger than a JS safe integer', async () => { + const huge = '115792089237316195423570985008687907853269984665640564039457584007913129639935' + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [{ token: TOKEN, from: SANCTIONED, to: SUBJECT, value: huge }], + minimums: [{ token: TOKEN, min: MIN }], + }) + expect((await inboundLabels(db)).length).toBe(1) + }) + + it('respects the per-chain minimum, not just the token', async () => { + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [{ chain: 'optimismSepolia', token: TOKEN, from: SANCTIONED, to: SUBJECT, value: MIN }], + minimums: [{ chain: 'baseSepolia', token: TOKEN, min: MIN }], // configured for a different chain + }) + expect(await inboundLabels(db)).toEqual([]) + }) +}) + +describe('computeProximity', () => { + it('merges both directions into one entry per subject', async () => { + await seedFixture(db, { + seeds: [ + { subject: SANCTIONED, label: 'sanctions' }, + { subject: MIXER, label: 'sanctioned_mixer' }, + ], + edges: [ + { token: TOKEN, from: SUBJECT, to: SANCTIONED, value: '1', logIndex: 0 }, + { token: TOKEN, from: SANCTIONED, to: SUBJECT, value: MIN, logIndex: 1 }, + { token: TOKEN, from: SUBJECT, to: MIXER, value: '1', logIndex: 2 }, + ], + minimums: [{ token: TOKEN, min: MIN }], + }) + expect(await computeProximity(db)).toEqual([ + { subject: SUBJECT, labels: ['mixer_exposure', 'sanctions_1hop', 'sanctions_1hop_inbound'] }, + ]) + }) + + it('deduplicates repeated edges to the same seed', async () => { + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [ + { token: TOKEN, from: SUBJECT, to: SANCTIONED, value: '1', logIndex: 0 }, + { token: TOKEN, from: SUBJECT, to: SANCTIONED, value: '2', logIndex: 1 }, + ], + }) + expect(await computeProximity(db)).toEqual([{ subject: SUBJECT, labels: ['sanctions_1hop'] }]) + }) + + it('returns nothing for an empty graph', async () => { + expect(await computeProximity(db)).toEqual([]) + }) + + it('returns nothing when there are edges but no seeds', async () => { + await seedFixture(db, { edges: [{ token: TOKEN, from: SUBJECT, to: OTHER, value: '1' }] }) + expect(await computeProximity(db)).toEqual([]) + }) + + it('is stable in subject order, so identical graphs produce identical feeds', async () => { + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [ + { token: TOKEN, from: OTHER, to: SANCTIONED, value: '1', logIndex: 0 }, + { token: TOKEN, from: SUBJECT, to: SANCTIONED, value: '1', logIndex: 1 }, + ], + }) + const subjects = (await computeProximity(db)).map((e) => e.subject) + expect(subjects).toEqual([...subjects].sort()) + }) +}) + +/** + * The inbound path is inert until thresholds exist, so the wiring from configuration into + * `token_minimums` is what actually turns `sanctions_1hop_inbound` on. + */ +describe('inbound thresholds from configuration', () => { + it('produces no inbound label until thresholds are applied', async () => { + const store = new IngestStore(db) + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [{ token: TOKEN, from: SANCTIONED, to: SUBJECT, value: MIN }], + }) + expect(await computeProximity(db)).toEqual([]) // nothing configured yet + + await store.replaceTokenMinimums([{ chain: 'baseSepolia', token: TOKEN, minValue: MIN }]) + expect(await computeProximity(db)).toEqual([{ subject: SUBJECT, labels: ['sanctions_1hop_inbound'] }]) + }) + + // Config is the only source of these values, so removing an entry must remove the threshold. + it('removes a threshold that is no longer configured', async () => { + const store = new IngestStore(db) + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [{ token: TOKEN, from: SANCTIONED, to: SUBJECT, value: MIN }], + }) + await store.replaceTokenMinimums([{ chain: 'baseSepolia', token: TOKEN, minValue: MIN }]) + expect((await computeProximity(db)).length).toBe(1) + + await store.replaceTokenMinimums([]) + expect(await computeProximity(db)).toEqual([]) + }) + + it('applies the configured value as the actual cutoff', async () => { + const store = new IngestStore(db) + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [{ token: TOKEN, from: SANCTIONED, to: SUBJECT, value: '5000' }], + }) + await store.replaceTokenMinimums([{ chain: 'baseSepolia', token: TOKEN, minValue: '5001' }]) + expect(await computeProximity(db)).toEqual([]) // just below + await store.replaceTokenMinimums([{ chain: 'baseSepolia', token: TOKEN, minValue: '5000' }]) + expect((await computeProximity(db)).length).toBe(1) // exactly at the cutoff counts + }) + + it('lowercases the token so a checksummed config entry still matches', async () => { + const store = new IngestStore(db) + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [{ token: TOKEN, from: SANCTIONED, to: SUBJECT, value: MIN }], + }) + await store.replaceTokenMinimums([ + { chain: 'baseSepolia', token: TOKEN.toUpperCase().replace('0X', '0x'), minValue: MIN }, + ]) + expect((await computeProximity(db)).length).toBe(1) + }) + + it('leaves outbound labels unaffected by thresholds', async () => { + const store = new IngestStore(db) + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [{ token: TOKEN, from: SUBJECT, to: SANCTIONED, value: '1' }], + }) + await store.replaceTokenMinimums([]) // no thresholds at all + expect(await computeProximity(db)).toEqual([{ subject: SUBJECT, labels: ['sanctions_1hop'] }]) + }) +}) + +/** + * Cross-chain sends. + * + * The burn/mint pair a bridged transfer leaves behind is not a counterparty relationship, and the + * zero address is excluded from every path, so a bridged send used to reach a sanctioned address + * without leaving anything the graph could see. The scanner now records the real pair as a `bridge` + * edge; these tests pin what such an edge may and may not be used for. + */ +describe('bridge edges', () => { + const BRIDGE = { kind: 'bridge', dstChain: 'optimismSepolia' } + + it('labels a sender who bridged straight to a sanctioned address', async () => { + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [{ token: TOKEN, from: SUBJECT, to: SANCTIONED, value: '1', ...BRIDGE }], + }) + expect(await computeProximity(db)).toEqual([{ subject: SUBJECT, labels: ['sanctions_1hop'] }]) + }) + + // The recipient of a bridge edge holds those funds on ANOTHER chain, so its activity on this one + // is unrelated money. Following it would manufacture a path that no funds could have taken. + it('never continues a path past a bridge edge', async () => { + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [ + // SUBJECT bridged to OTHER (funds now on OP), and OTHER separately paid a sanctioned + // address on this chain. SUBJECT is not 2 hops from the seed by way of that. + { token: TOKEN, from: SUBJECT, to: OTHER, value: MIN, block: 100, logIndex: 0, ...BRIDGE }, + { token: TOKEN, from: OTHER, to: SANCTIONED, value: MIN, block: 101, logIndex: 1 }, + ], + minimums: [{ token: TOKEN, min: MIN }], + }) + expect(await computeProximity(db)).toEqual([{ subject: OTHER, labels: ['sanctions_1hop'] }]) + }) + + // A bridge as the LAST hop is sound: the funds moved same-chain to the intermediary, which then + // bridged them onward to the seed. + it('allows a bridge as the final hop of a path', async () => { + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [ + { token: TOKEN, from: SUBJECT, to: OTHER, value: '1', block: 100, logIndex: 0 }, + { token: TOKEN, from: OTHER, to: SANCTIONED, value: MIN, block: 101, logIndex: 1, ...BRIDGE }, + ], + minimums: [{ token: TOKEN, min: MIN }], + }) + expect(await computeProximity(db)).toEqual([ + { subject: SUBJECT, labels: ['sanctions_2hop'] }, + { subject: OTHER, labels: ['sanctions_1hop'] }, + ]) + }) + + it('applies the inbound minimum to a bridge edge like any other', async () => { + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [{ token: TOKEN, from: SANCTIONED, to: SUBJECT, value: '1', ...BRIDGE }], + minimums: [{ token: TOKEN, min: MIN }], + }) + // Below the threshold: a bridged dusting is still dusting. + expect(await computeProximity(db)).toEqual([]) + }) +}) + +describe('exposureCounts', () => { + it('counts distinct seeds a subject touched', async () => { + await seedFixture(db, { + seeds: [ + { subject: SANCTIONED, label: 'sanctions' }, + { subject: MIXER, label: 'sanctioned_mixer' }, + ], + edges: [ + { token: TOKEN, from: SUBJECT, to: SANCTIONED, value: '1', logIndex: 0 }, + { token: TOKEN, from: SUBJECT, to: MIXER, value: '1', logIndex: 1 }, + { token: TOKEN, from: SUBJECT, to: SANCTIONED, value: '5', logIndex: 2 }, // same seed again + ], + }) + const counts = await exposureCounts(db) + expect(counts.find((c) => c.subject === SUBJECT)?.seeds).toBe(2) + }) +}) diff --git a/indexer/test/scanner.spec.ts b/indexer/test/scanner.spec.ts new file mode 100644 index 0000000..455fce9 --- /dev/null +++ b/indexer/test/scanner.spec.ts @@ -0,0 +1,412 @@ +import { ethers } from 'ethers' +import pino from 'pino' +import { beforeEach, describe, expect, it } from 'vitest' + +import { + type LogSource, + decodePacketRecipient, + dvnInterface, + endpointInterface, + erc20Interface, + oftInterface, +} from '../src/chain/events' +import { scanChainOnce } from '../src/ingest/scanner' +import { IngestStore } from '../src/ingest/store' + +import { applySchema, memDb } from './helpers/memdb' + +const silent = pino({ level: 'silent' }) +const CHAIN = { key: 'baseSepolia', dvn: '0x' + 'd'.repeat(40), endpoint: '0x' + 'f'.repeat(40) } +const TOKEN = '0x' + 'e'.repeat(40) +const A = '0x' + '1'.repeat(40) +const B = '0x' + '2'.repeat(40) +const PAYLOAD = '0x' + 'a'.repeat(64) + +let db: ReturnType +let store: IngestStore + +beforeEach(() => { + db = memDb() + applySchema(db) + store = new IngestStore(db) +}) + +/** A fake chain: blocks with deterministic hashes plus whatever logs the test places. */ +class FakeChain implements LogSource { + /** hash suffix per height, so a reorg can be simulated by changing it. */ + private hashes = new Map() + logs: ethers.providers.Log[] = [] + + constructor(private head: number) {} + + setHead(head: number): void { + this.head = head + } + + /** Rewrite history from `fromHeight` up, as a reorg would. */ + fork(fromHeight: number, marker: string): void { + for (const height of [...this.hashes.keys()]) { + if (height >= fromHeight) this.hashes.set(height, marker) + } + for (let h = fromHeight; h <= this.head; h++) this.hashes.set(h, marker) + } + + private hashOf(height: number): string { + const marker = this.hashes.get(height) ?? 'a' + return '0x' + marker.repeat(1).padStart(2, '0').repeat(1) + String(height).padStart(62, '0') + } + + async getBlockNumber(): Promise { + return this.head + } + + async getBlock(blockNumber: number): Promise<{ hash: string; parentHash: string; timestamp: number } | null> { + if (blockNumber > this.head || blockNumber < 0) return null + // Deterministic, monotonic block times: 2s apart from a fixed genesis. + return { + hash: this.hashOf(blockNumber), + parentHash: this.hashOf(blockNumber - 1), + timestamp: 1_700_000_000 + blockNumber * 2, + } + } + + async getLogs(filter: { address?: string; topics?: (string | null)[]; fromBlock: number; toBlock: number }) { + return this.logs.filter((l) => { + if (l.blockNumber < filter.fromBlock || l.blockNumber > filter.toBlock) return false + if (filter.address && l.address.toLowerCase() !== filter.address.toLowerCase()) return false + const want = filter.topics?.[0] + if (!want) return true + const list = Array.isArray(want) ? (want as unknown as string[]) : [want] + return list.includes(l.topics[0]) + }) + } +} + +function verdictLog(blockNumber: number, logIndex: number): ethers.providers.Log { + const encoded = dvnInterface.encodeEventLog(dvnInterface.getEvent('RiskVerdict'), [ + PAYLOAD, + 3, + 100, + ethers.BigNumber.from(1), + '0x' + 'b'.repeat(64), + ]) + return { + blockNumber, + blockHash: '0x', + transactionIndex: 0, + removed: false, + address: CHAIN.dvn, + data: encoded.data, + topics: encoded.topics, + transactionHash: '0x' + String(blockNumber * 100 + logIndex).padStart(64, '0'), + logIndex, + } +} + +function transferLog(blockNumber: number, logIndex: number, value: string): ethers.providers.Log { + const encoded = erc20Interface.encodeEventLog(erc20Interface.getEvent('Transfer'), [A, B, ethers.BigNumber.from(value)]) + return { + blockNumber, + blockHash: '0x', + transactionIndex: 0, + removed: false, + address: TOKEN, + data: encoded.data, + topics: encoded.topics, + transactionHash: '0x' + String(900000 + blockNumber * 100 + logIndex).padStart(64, '0'), + logIndex, + } +} + +function deps(chainSource: FakeChain, overrides: Partial[0]> = {}) { + return { + chain: CHAIN, + source: chainSource, + store, + trackedTokens: [TOKEN], + confirmations: 5, + scanWindow: 50, + scanChunk: 1000, + reorgDepth: 32, + logger: silent, + ...overrides, + } +} + +describe('scanChainOnce', () => { + it('ingests verdicts and transfers up to the safe head', async () => { + const chain = new FakeChain(100) + chain.logs = [verdictLog(90, 0), transferLog(91, 0, '1000')] + const result = await scanChainOnce(deps(chain)) + expect(result.to).toBe(95) // 100 - 5 confirmations + expect(result.verdicts).toBe(1) + expect(result.transfers).toBe(1) + expect(await store.getCursor(CHAIN.key)).toBe(95) + expect((await store.counts()).risk_verdicts).toBe(1) + expect((await store.counts()).edges).toBe(1) + }) + + it('does not read past the confirmation depth', async () => { + const chain = new FakeChain(100) + chain.logs = [verdictLog(97, 0)] // inside the unconfirmed window + const result = await scanChainOnce(deps(chain)) + expect(result.verdicts).toBe(0) + }) + + it('is idempotent — rescanning the same range inserts nothing new', async () => { + const chain = new FakeChain(100) + chain.logs = [verdictLog(90, 0), transferLog(91, 0, '1000')] + await scanChainOnce(deps(chain)) + // Force a rescan of the same range by rewinding the cursor only. + await store.setCursor(CHAIN.key, 80) + await scanChainOnce(deps(chain)) + expect((await store.counts()).risk_verdicts).toBe(1) + expect((await store.counts()).edges).toBe(1) + }) + + it('does nothing when the safe head has not advanced', async () => { + const chain = new FakeChain(100) + await store.setCursor(CHAIN.key, 95) + const result = await scanChainOnce(deps(chain)) + expect(result.verdicts).toBe(0) + expect(await store.getCursor(CHAIN.key)).toBe(95) + }) + + // Scan lag is head - cursor, and this is the only place the head is read. Reporting it even on + // an idle tick is what keeps the lag gauge from sitting at a stale value. + it('reports the chain head on every tick', async () => { + const chain = new FakeChain(100) + expect((await scanChainOnce(deps(chain))).head).toBe(100) + await store.setCursor(CHAIN.key, 95) + expect((await scanChainOnce(deps(chain))).head).toBe(100) + }) + + it('advances in bounded chunks', async () => { + const chain = new FakeChain(1000) + await store.setCursor(CHAIN.key, 0) + chain.logs = [verdictLog(500, 0)] + const result = await scanChainOnce(deps(chain, { scanChunk: 100 })) + expect(result.to).toBe(995) + expect(result.verdicts).toBe(1) + }) + + // ERC-721 shares the Transfer topic but keeps all three parameters indexed, so its logs do + // not decode as ERC-20. Before this was handled, one NFT contract in TRACKED_TOKENS aborted + // the same chunk every tick — the cursor froze and never advanced again. + it('skips undecodable Transfer logs instead of freezing the cursor', async () => { + const chain = new FakeChain(100) + const erc721Transfer: ethers.providers.Log = { + blockNumber: 90, + blockHash: '0x', + transactionIndex: 0, + removed: false, + address: TOKEN, + data: '0x', // ERC-721: value lives in topics[3], not data + topics: [ + erc20Interface.getEventTopic('Transfer'), + ethers.utils.hexZeroPad(A, 32), + ethers.utils.hexZeroPad(B, 32), + ethers.utils.hexZeroPad('0x01', 32), // tokenId + ], + transactionHash: '0x' + '7'.repeat(64), + logIndex: 0, + } + chain.logs = [erc721Transfer, transferLog(91, 0, '1000')] + + const result = await scanChainOnce(deps(chain)) + expect(result.transfers).toBe(1) // the real ERC-20 transfer still lands + expect(await store.getCursor(CHAIN.key)).toBe(95) // cursor advanced past the bad log + expect((await store.counts()).edges).toBe(1) + }) + + describe('reorg handling', () => { + it('rolls back and rescans when the stored hash no longer matches', async () => { + const chain = new FakeChain(100) + chain.logs = [verdictLog(90, 0)] + await scanChainOnce(deps(chain)) + expect(await store.getCursor(CHAIN.key)).toBe(95) + expect((await store.counts()).risk_verdicts).toBe(1) + + // History is rewritten from 93 up. The log at 90 is still canonical, so the rescan + // re-ingests it — the row count is unchanged because the insert is idempotent. + chain.fork(93, 'f') + const result = await scanChainOnce(deps(chain)) + + expect(result.reorgDepth).toBeGreaterThan(0) + expect((await store.counts()).risk_verdicts).toBe(1) + expect(await store.getCursor(CHAIN.key)).toBe(95) + }) + + it('discards rows that the new canonical chain no longer contains', async () => { + const chain = new FakeChain(100) + chain.logs = [verdictLog(94, 0)] // inside the range that will be rewritten + await scanChainOnce(deps(chain)) + expect((await store.counts()).risk_verdicts).toBe(1) + + chain.fork(93, 'f') + chain.logs = [] // the log does not exist on the new chain + await scanChainOnce(deps(chain)) + + expect((await store.counts()).risk_verdicts).toBe(0) + }) + + it('re-ingests the replacement logs after a reorg', async () => { + const chain = new FakeChain(100) + chain.logs = [verdictLog(94, 0)] + await scanChainOnce(deps(chain)) + + chain.fork(93, 'f') + chain.logs = [verdictLog(94, 1)] // a different log at the same height + await scanChainOnce(deps(chain)) + + const rows = await db.query<{ log_index: number }>('SELECT log_index FROM risk_verdicts') + expect(rows.rows.length).toBe(1) + expect(Number(rows.rows[0].log_index)).toBe(1) + }) + + // Silently trusting rows we know are wrong would be worse than stopping and being noticed. + it('aborts loudly when the reorg is deeper than REORG_DEPTH', async () => { + const chain = new FakeChain(100) + chain.logs = [verdictLog(90, 0)] + await scanChainOnce(deps(chain)) + + chain.fork(0, 'f') // everything rewritten + await expect(scanChainOnce(deps(chain, { reorgDepth: 3 }))).rejects.toThrow(/deeper than REORG_DEPTH/) + }) + + it('treats a cold cursor as no reorg', async () => { + const chain = new FakeChain(100) + const result = await scanChainOnce(deps(chain)) + expect(result.reorgDepth).toBe(0) + }) + }) + + it('collects no edges when no tokens are tracked', async () => { + const chain = new FakeChain(100) + chain.logs = [transferLog(90, 0, '1000')] + const result = await scanChainOnce(deps(chain, { trackedTokens: [] })) + expect(result.transfers).toBe(0) + }) +}) + +/** + * Cross-chain sends. + * + * A bridged transfer is a burn here and a mint there, so the burn/mint pair alone says only that + * the supply moved — and when the DVN blocks the packet, the destination half never happens at all. + * The sender and the recipient are both knowable on this side: `OFTSent` names one, the packet the + * other, and the guid ties them together. + */ +describe('scanChainOnce: cross-chain sends', () => { + const DST_EID = 40232 + + /** `header(81) ‖ guid(32) ‖ message`, with the recipient as the message's opening word. */ + function packet(guid: string, to: string): string { + const header = '01' + '00'.repeat(80) + const message = '00'.repeat(12) + to.slice(2) + '00'.repeat(8) + return '0x' + header + guid.slice(2) + message + } + + function oftSentLog(blockNumber: number, logIndex: number, guid: string, from: string, value: string) { + const encoded = oftInterface.encodeEventLog(oftInterface.getEvent('OFTSent'), [ + guid, + DST_EID, + from, + ethers.BigNumber.from(value), + ethers.BigNumber.from(value), + ]) + return { + blockNumber, blockHash: '0x', transactionIndex: 0, removed: false, address: TOKEN, + data: encoded.data, topics: encoded.topics, + transactionHash: '0x' + String(700000 + blockNumber).padStart(64, '0'), logIndex, + } as ethers.providers.Log + } + + function packetSentLog(blockNumber: number, logIndex: number, guid: string, to: string) { + const encoded = endpointInterface.encodeEventLog(endpointInterface.getEvent('PacketSent'), [ + packet(guid, to), '0x', '0x' + '9'.repeat(40), + ]) + return { + blockNumber, blockHash: '0x', transactionIndex: 0, removed: false, address: CHAIN.endpoint, + data: encoded.data, topics: encoded.topics, + transactionHash: '0x' + String(700000 + blockNumber).padStart(64, '0'), logIndex, + } as ethers.providers.Log + } + + const guid = (n: number) => '0x' + String(n).padStart(64, '0') + + it('records the real counterparties as a bridge edge', async () => { + const chain = new FakeChain(100) + chain.logs = [oftSentLog(90, 0, guid(1), A, '5000'), packetSentLog(90, 1, guid(1), B)] + const result = await scanChainOnce(deps(chain, { chainByEid: () => 'optimismSepolia' })) + + expect(result.bridgeSends).toBe(1) + const { rows } = await db.query<{ from_addr: string; to_addr: string; value: string; kind: string; dst_chain: string }>( + 'SELECT from_addr, to_addr, value, kind, dst_chain FROM edges', + ) + expect(rows).toHaveLength(1) + expect(rows[0].from_addr).toBe(A.toLowerCase()) + expect(rows[0].to_addr).toBe(B.toLowerCase()) + expect(String(rows[0].value)).toBe('5000') + expect(rows[0].kind).toBe('bridge') + expect(rows[0].dst_chain).toBe('optimismSepolia') + }) + + // The burn is a separate log in the same transaction, so both must land without colliding on the + // (chain, tx_hash, log_index) key. + it('keeps the burn and the bridge edge as separate rows', async () => { + const chain = new FakeChain(100) + const burn = { ...transferLog(90, 2, '5000'), transactionHash: '0x' + String(700090).padStart(64, '0') } + chain.logs = [oftSentLog(90, 0, guid(1), A, '5000'), packetSentLog(90, 1, guid(1), B), burn] + const result = await scanChainOnce(deps(chain)) + expect(result.transfers).toBe(1) + expect(result.bridgeSends).toBe(1) + const { rows } = await db.query<{ kind: string }>('SELECT kind FROM edges ORDER BY log_index') + expect(rows.map((r) => r.kind)).toEqual(['bridge', 'transfer']) + }) + + // Two sends in one transaction share a tx hash, so the guid — not the transaction — has to be + // what pairs a send with its recipient. + it('pairs batched sends by guid rather than by transaction', async () => { + const chain = new FakeChain(100) + const C = '0x' + '3'.repeat(40) + chain.logs = [ + oftSentLog(90, 0, guid(1), A, '100'), + oftSentLog(90, 1, guid(2), A, '200'), + packetSentLog(90, 2, guid(2), C), + packetSentLog(90, 3, guid(1), B), + ] + await scanChainOnce(deps(chain)) + const { rows } = await db.query<{ to_addr: string; value: string }>( + "SELECT to_addr, value FROM edges WHERE kind = 'bridge' ORDER BY value", + ) + expect(rows.map((r) => [r.to_addr, String(r.value)])).toEqual([ + [B.toLowerCase(), '100'], + [C.toLowerCase(), '200'], + ]) + }) + + it('skips a send whose recipient cannot be read rather than guessing one', async () => { + const chain = new FakeChain(100) + chain.logs = [oftSentLog(90, 0, guid(1), A, '5000')] // no PacketSent + const result = await scanChainOnce(deps(chain)) + expect(result.bridgeSends).toBe(0) + expect((await db.query('SELECT 1 FROM edges')).rowCount).toBe(0) + }) + + it('records the edge even when the destination chain is not indexed here', async () => { + const chain = new FakeChain(100) + chain.logs = [oftSentLog(90, 0, guid(1), A, '5000'), packetSentLog(90, 1, guid(1), B)] + await scanChainOnce(deps(chain, { chainByEid: () => undefined })) + const { rows } = await db.query<{ dst_chain: string | null }>("SELECT dst_chain FROM edges WHERE kind = 'bridge'") + expect(rows).toHaveLength(1) + expect(rows[0].dst_chain).toBeNull() + }) + + it('reads the recipient from the message, not from the guid that precedes it', () => { + const decoded = decodePacketRecipient(packet(guid(7), B)) + expect(decoded?.guid).toBe(guid(7)) + expect(decoded?.to).toBe(B.toLowerCase()) + expect(decodePacketRecipient('0x1234')).toBeUndefined() + }) +}) diff --git a/indexer/test/verification.spec.ts b/indexer/test/verification.spec.ts new file mode 100644 index 0000000..9485b15 --- /dev/null +++ b/indexer/test/verification.spec.ts @@ -0,0 +1,343 @@ +import pino from 'pino' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import { collectEntries } from '../src/feed/builder' +import { refreshVerification, unverifiedContracts } from '../src/verify/refresh' +import { + DEFAULT_SOURCIFY_URL, + type Fetcher, + RateLimited, + lookupMany, + lookupOne, + parseMatch, +} from '../src/verify/sourcify' + +import { applySchema, memDb, seedFixture } from './helpers/memdb' + +const silent = pino({ level: 'silent' }) +const CHAIN = { key: 'baseSepolia', chainId: 84532 } +const CODE = '0x60006000' + +const VERIFIED = '0x' + '1'.repeat(40) +const UNVERIFIED = '0x' + '2'.repeat(40) +const EOA = '0x' + '3'.repeat(40) +const SANCTIONED = '0x' + 'a'.repeat(40) +const TOKEN = '0x' + 'd'.repeat(40) + +/** A v2 success body for a verified contract. */ +const okVerified = (address: string) => + JSON.stringify({ match: 'exact_match', creationMatch: 'exact_match', runtimeMatch: 'exact_match', chainId: '84532', address }) + +/** A v2 success body for a contract Sourcify knows but has no source for. */ +const okUnverified = (address: string) => + JSON.stringify({ match: null, creationMatch: null, runtimeMatch: null, chainId: '84532', address }) + +/** Respond per address; anything unlisted gets a 404 (Sourcify has no record). */ +function fetcherFor(map: Record): Fetcher { + return async (url: string) => { + const address = (url.split('/').pop() ?? '').toLowerCase() + return map[address] ?? { status: 404, body: JSON.stringify({ error: 'not found' }) } + } +} + +let db: ReturnType + +beforeEach(() => { + db = memDb() + applySchema(db) +}) + +function deps(overrides: Partial[0]> = {}) { + return { + db, + chain: CHAIN, + reader: { getCode: async () => CODE }, + trackedTokens: [] as readonly string[], + batchSize: 50, + ttlSec: 604_800, + logger: silent, + now: () => 1_800_000_000_000, + ...overrides, + } +} + +describe('parseMatch', () => { + it('reads a v2 match as verified', () => { + expect(parseMatch(okVerified(VERIFIED))).toBe('verified') + expect(parseMatch(JSON.stringify({ match: 'match', creationMatch: null, runtimeMatch: 'match' }))).toBe('verified') + }) + + // An explicit null match is a real answer: Sourcify knows the contract and has no source. + it('reads a null match as unverified', () => { + expect(parseMatch(okUnverified(UNVERIFIED))).toBe('unverified') + }) + + it('accepts a legacy self-hosted status', () => { + expect(parseMatch(JSON.stringify({ match: 'perfect' }))).toBe('verified') + }) + + // Biasing toward not labelling is the safe direction — a missed label costs a weak signal, + // a false one inflates every score it touches. + it('returns unknown for a body it cannot interpret', () => { + expect(parseMatch('not json')).toBe('unknown') + expect(parseMatch(JSON.stringify({ error: 'Service Unavailable' }))).toBe('unknown') + expect(parseMatch(JSON.stringify([1, 2, 3]))).toBe('unknown') + expect(parseMatch('null')).toBe('unknown') + }) +}) + +describe('lookupOne', () => { + it('targets the v2 contract endpoint with the chain id', async () => { + const fetcher = vi.fn(async (_url: string) => ({ status: 200, body: okVerified(VERIFIED) })) + await lookupOne(VERIFIED, 84532, { fetcher }) + expect(fetcher.mock.calls[0][0]).toBe(`${DEFAULT_SOURCIFY_URL}/v2/contract/84532/${VERIFIED}`) + }) + + it('honours a custom verifier url', async () => { + const fetcher = vi.fn(async (_url: string) => ({ status: 200, body: okVerified(VERIFIED) })) + await lookupOne(VERIFIED, 84532, { fetcher, baseUrl: 'https://my-sourcify.internal/server/' }) + expect(fetcher.mock.calls[0][0]).toContain('https://my-sourcify.internal/server/v2/contract/84532/') + }) + + // 404 means Sourcify has no source for this contract — a definitive answer, not a failure. + it('treats 404 as unverified', async () => { + const fetcher: Fetcher = async () => ({ status: 404, body: '{}' }) + expect(await lookupOne(UNVERIFIED, 84532, { fetcher })).toBe('unverified') + }) + + // The v1 brownout returns exactly this. It must never be read as "unverified". + it('treats a 503 brownout as unknown', async () => { + const fetcher: Fetcher = async () => ({ + status: 503, + body: JSON.stringify({ error: 'Service Unavailable - API v1 Brownout' }), + }) + expect(await lookupOne(UNVERIFIED, 84532, { fetcher })).toBe('unknown') + }) + + it('treats a 500 as unknown', async () => { + const fetcher: Fetcher = async () => ({ status: 500, body: 'oops' }) + expect(await lookupOne(UNVERIFIED, 84532, { fetcher })).toBe('unknown') + }) + + it('throws RateLimited on 429 so the caller can back off', async () => { + const fetcher: Fetcher = async () => ({ status: 429, body: 'slow down' }) + await expect(lookupOne(UNVERIFIED, 84532, { fetcher })).rejects.toBeInstanceOf(RateLimited) + }) + + it('rejects on timeout', async () => { + await expect( + lookupOne(VERIFIED, 84532, { fetcher: () => new Promise(() => {}), timeoutMs: 10 }), + ).rejects.toThrow(/timed out/) + }) +}) + +describe('lookupMany', () => { + it('resolves each address independently', async () => { + const fetcher = fetcherFor({ + [VERIFIED]: { status: 200, body: okVerified(VERIFIED) }, + [UNVERIFIED]: { status: 200, body: okUnverified(UNVERIFIED) }, + }) + const { statuses, rateLimited } = await lookupMany([VERIFIED, UNVERIFIED], 84532, { fetcher }) + expect(rateLimited).toBe(false) + expect(statuses.get(VERIFIED)).toBe('verified') + expect(statuses.get(UNVERIFIED)).toBe('unverified') + }) + + it('marks a single transport failure unknown without affecting the others', async () => { + let first = true + const fetcher: Fetcher = async (url) => { + if (first) { + first = false + throw new Error('ECONNRESET') + } + return { status: 200, body: okVerified(url.split('/').pop()!) } + } + const { statuses } = await lookupMany([UNVERIFIED, VERIFIED], 84532, { fetcher }) + expect(statuses.get(UNVERIFIED)).toBe('unknown') + expect(statuses.get(VERIFIED)).toBe('verified') + }) + + // Pushing through a 429 would only get the remaining answers refused too. + it('stops the pass on a rate limit and reports what it got', async () => { + const fetcher = vi.fn(async (url: string) => { + if (url.endsWith(VERIFIED)) return { status: 200, body: okVerified(VERIFIED) } + return { status: 429, body: 'slow down' } + }) + const { statuses, rateLimited } = await lookupMany([VERIFIED, UNVERIFIED, EOA], 84532, { fetcher }) + expect(rateLimited).toBe(true) + expect(statuses.get(VERIFIED)).toBe('verified') + expect(statuses.has(EOA)).toBe(false) // never attempted + expect(fetcher).toHaveBeenCalledTimes(2) + }) +}) + +describe('refreshVerification', () => { + it('resolves tracked tokens even before any edges exist', async () => { + const fetcher = fetcherFor({ [TOKEN]: { status: 200, body: okVerified(TOKEN) } }) + const result = await refreshVerification(deps({ trackedTokens: [TOKEN], sourcify: { fetcher } })) + expect(result.contracts).toBe(1) + expect(result.verified).toBe(1) + expect(await unverifiedContracts(db)).toEqual([]) + }) + + it('records an unverified contract', async () => { + const fetcher = fetcherFor({ [UNVERIFIED]: { status: 200, body: okUnverified(UNVERIFIED) } }) + const result = await refreshVerification(deps({ trackedTokens: [UNVERIFIED], sourcify: { fetcher } })) + expect(result.unverified).toBe(1) + expect(await unverifiedContracts(db)).toEqual([UNVERIFIED]) + }) + + it('records a 404 contract as unverified', async () => { + const result = await refreshVerification(deps({ trackedTokens: [UNVERIFIED], sourcify: { fetcher: fetcherFor({}) } })) + expect(result.unverified).toBe(1) + expect(await unverifiedContracts(db)).toEqual([UNVERIFIED]) + }) + + // Asking a verifier about an EOA is wasted budget, and an EOA is not a contract to label. + it('records an EOA and never asks the verifier about it', async () => { + const fetcher = vi.fn(async (_url: string) => ({ status: 404, body: '{}' })) + const result = await refreshVerification( + deps({ trackedTokens: [EOA], reader: { getCode: async () => '0x' }, sourcify: { fetcher } }), + ) + expect(result.contracts).toBe(0) + expect(fetcher).not.toHaveBeenCalled() + expect(await unverifiedContracts(db)).toEqual([]) + }) + + it('picks up contract participants from the edge graph', async () => { + await seedFixture(db, { edges: [{ token: TOKEN, from: UNVERIFIED, to: VERIFIED, value: '1' }] }) + const fetcher = fetcherFor({ + [VERIFIED]: { status: 200, body: okVerified(VERIFIED) }, + [UNVERIFIED]: { status: 200, body: okUnverified(UNVERIFIED) }, + }) + const result = await refreshVerification(deps({ sourcify: { fetcher } })) + expect(result.contracts).toBe(2) + expect(await unverifiedContracts(db)).toEqual([UNVERIFIED]) + }) + + /** + * The behaviour this whole design exists for: a verifier outage must leave status unknown, not + * label every contract in the graph as unverified. This is exactly the v1 brownout response. + */ + it('leaves status UNKNOWN when the verifier is unavailable', async () => { + const fetcher: Fetcher = async () => ({ status: 503, body: JSON.stringify({ error: 'brownout' }) }) + const result = await refreshVerification(deps({ trackedTokens: [UNVERIFIED], sourcify: { fetcher } })) + expect(result.unverified).toBe(0) + expect(result.unknown).toBe(1) + expect(await unverifiedContracts(db)).toEqual([]) + + const rows = await db.query<{ verified: boolean | null }>('SELECT verified FROM contract_status') + // Either nothing was written, or written with an unknown verdict — never `false`. + expect(rows.rows.every((r) => r.verified === null || r.verified === undefined)).toBe(true) + }) + + it('leaves status unknown on a rate limit and flags it', async () => { + const fetcher: Fetcher = async () => ({ status: 429, body: 'slow down' }) + const result = await refreshVerification(deps({ trackedTokens: [UNVERIFIED], sourcify: { fetcher } })) + expect(result.rateLimited).toBe(true) + expect(result.unknown).toBe(1) + expect(await unverifiedContracts(db)).toEqual([]) + }) + + it('retries an unknown address on the next pass', async () => { + let unavailable = true + const fetcher: Fetcher = async (url) => + unavailable + ? { status: 503, body: '{}' } + : { status: 200, body: okUnverified(url.split('/').pop()!) } + + await refreshVerification(deps({ trackedTokens: [UNVERIFIED], sourcify: { fetcher } })) + expect(await unverifiedContracts(db)).toEqual([]) + unavailable = false + await refreshVerification(deps({ trackedTokens: [UNVERIFIED], sourcify: { fetcher } })) + expect(await unverifiedContracts(db)).toEqual([UNVERIFIED]) + }) + + it('does not re-ask about an address already settled inside the TTL', async () => { + const fetcher = vi.fn(async (url: string) => ({ status: 200, body: okUnverified(url.split('/').pop()!) })) + await refreshVerification(deps({ trackedTokens: [UNVERIFIED], sourcify: { fetcher } })) + expect(fetcher).toHaveBeenCalledOnce() + await refreshVerification(deps({ trackedTokens: [UNVERIFIED], sourcify: { fetcher } })) + expect(fetcher).toHaveBeenCalledOnce() // served from the cached answer + }) + + it('re-asks once the TTL has passed', async () => { + const fetcher = vi.fn(async (url: string) => ({ status: 200, body: okUnverified(url.split('/').pop()!) })) + const t0 = 1_800_000_000_000 + await refreshVerification(deps({ trackedTokens: [UNVERIFIED], sourcify: { fetcher }, now: () => t0 })) + await refreshVerification( + deps({ trackedTokens: [UNVERIFIED], sourcify: { fetcher }, ttlSec: 100, now: () => t0 + 200_000 }), + ) + expect(fetcher).toHaveBeenCalledTimes(2) + }) + + it('bounds how many addresses it resolves per pass', async () => { + await seedFixture(db, { + edges: [ + { token: TOKEN, from: '0x' + '4'.repeat(40), to: '0x' + '5'.repeat(40), value: '1', logIndex: 0 }, + { token: TOKEN, from: '0x' + '6'.repeat(40), to: '0x' + '7'.repeat(40), value: '1', logIndex: 1 }, + ], + }) + const result = await refreshVerification(deps({ batchSize: 2, sourcify: { fetcher: fetcherFor({}) } })) + expect(result.inspected).toBe(2) + }) + + it('survives a getCode failure without aborting the pass', async () => { + let first = true + const reader = { + getCode: async () => { + if (first) { + first = false + throw new Error('rpc down') + } + return CODE + }, + } + const result = await refreshVerification( + deps({ trackedTokens: [UNVERIFIED, VERIFIED], reader, sourcify: { fetcher: fetcherFor({}) } }), + ) + expect(result.inspected).toBe(2) + expect(result.contracts).toBe(1) // the second one still resolved + }) +}) + +describe('feed entries with verification', () => { + it('attaches unverified_contract to an address the graph already labels', async () => { + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [{ token: TOKEN, from: UNVERIFIED, to: SANCTIONED, value: '1' }], + }) + await refreshVerification(deps({ sourcify: { fetcher: fetcherFor({}) } })) + expect(await collectEntries(db)).toEqual([ + { address: UNVERIFIED, labels: ['sanctions_1hop', 'unverified_contract'] }, + ]) + }) + + it('does not attach the label to a verified contract', async () => { + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [{ token: TOKEN, from: VERIFIED, to: SANCTIONED, value: '1' }], + }) + await refreshVerification( + deps({ sourcify: { fetcher: fetcherFor({ [VERIFIED]: { status: 200, body: okVerified(VERIFIED) } }) } }), + ) + expect(await collectEntries(db)).toEqual([{ address: VERIFIED, labels: ['sanctions_1hop'] }]) + }) + + // A verifier outage must not silently add labels to every address in the graph. + it('does not attach the label when status is unknown', async () => { + await seedFixture(db, { + seeds: [{ subject: SANCTIONED, label: 'sanctions' }], + edges: [{ token: TOKEN, from: UNVERIFIED, to: SANCTIONED, value: '1' }], + }) + await refreshVerification(deps({ sourcify: { fetcher: async () => ({ status: 503, body: '{}' }) } })) + expect(await collectEntries(db)).toEqual([{ address: UNVERIFIED, labels: ['sanctions_1hop'] }]) + }) + + // On its own the label scores 20, below the delay threshold, so publishing every unverified + // contract on a testnet would be feed weight for no effect. + it('does not publish an unverified contract the graph says nothing else about', async () => { + await refreshVerification(deps({ trackedTokens: [UNVERIFIED], sourcify: { fetcher: fetcherFor({}) } })) + expect(await collectEntries(db)).toEqual([]) + }) +}) diff --git a/indexer/tsconfig.json b/indexer/tsconfig.json new file mode 100644 index 0000000..a47afb4 --- /dev/null +++ b/indexer/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "noEmit": true, + "types": ["node"], + "lib": ["es2020", "dom"], + "moduleResolution": "node" + }, + "include": ["**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/indexer/vitest.config.ts b/indexer/vitest.config.ts new file mode 100644 index 0000000..cee630b --- /dev/null +++ b/indexer/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config' + +// Standalone package (own package.json + Docker image), so it carries its own vitest config — +// same reason the worker does. Without it, `cd indexer && pnpm test` walks up to the repo-root +// config whose globs do not resolve from here. +export default defineConfig({ + test: { + include: ['test/**/*.spec.ts'], + }, +}) diff --git a/layerzero.config.ts b/layerzero.config.ts index f3a431a..93add09 100644 --- a/layerzero.config.ts +++ b/layerzero.config.ts @@ -1,10 +1,33 @@ import { EndpointId } from '@layerzerolabs/lz-definitions' -const DVN_BASE = process.env.DVN_BASE_SEPOLIA || '0x0000000000000000000000000000000000000000' -const DVN_OPT = process.env.DVN_OPTIMISM_SEPOLIA || '0x0000000000000000000000000000000000000000' +import { OAPP_CONTRACT } from './oapp.contract' -const base = { eid: EndpointId.BASESEP_V2_TESTNET, contractName: 'ToyOFT' } -const opt = { eid: EndpointId.OPTSEP_V2_TESTNET, contractName: 'ToyOFT' } +const EVM_ADDRESS = /^0x[0-9a-fA-F]{40}$/ + +/** + * Read a deployed ComplianceDVN address, refusing to fall back to a placeholder. + * + * Wiring is the step that tells the ULN which DVN a pathway REQUIRES. A zero or malformed address + * here does not fail loudly at wire time — it succeeds, and every subsequent message on that + * pathway becomes permanently unverifiable because the required DVN has no code to verify with. + * Failing here costs one clear error; not failing costs a re-wire and stuck packets. + */ +function requireDvn(envVar: string): string { + const value = (process.env[envVar] ?? '').trim() + if (!EVM_ADDRESS.test(value)) { + throw new Error( + `${envVar} must be the deployed ComplianceDVN address (0x + 40 hex) before wiring. ` + + `Deploy first, then set it in .env — wiring with a placeholder would require a DVN that cannot verify.` + ) + } + return value +} + +const DVN_BASE = requireDvn('DVN_BASE_SEPOLIA') +const DVN_OPT = requireDvn('DVN_OPTIMISM_SEPOLIA') + +const base = { eid: EndpointId.BASESEP_V2_TESTNET, contractName: OAPP_CONTRACT } +const opt = { eid: EndpointId.OPTSEP_V2_TESTNET, contractName: OAPP_CONTRACT } // One ULN config per chain, referencing THAT chain's ComplianceDVN as the single required DVN. const ulnBase = { diff --git a/oapp.contract.ts b/oapp.contract.ts new file mode 100644 index 0000000..1895f05 --- /dev/null +++ b/oapp.contract.ts @@ -0,0 +1,16 @@ +/** + * Which OApp the DVN is wired to, in one place. + * + * Wiring calls `EndpointV2.setConfig(oapp, lib, params)`, which only the OApp or its delegate may + * do — so the OApp named here has to be one the signing key controls. Switching targets therefore + * means switching this value, and it is referenced by the LayerZero config, the demo tasks, and + * the preflight check so they cannot drift apart. + * + * `ToyOFT` at 0xdEc1591D39ECb8278d1a2256a5BF17507A375F00 is owned by a key held elsewhere + * (0x69bd4d7e…210c), so it cannot be wired from this repo's deployer. `MyOFT` is deployed by, and + * owned by, whoever runs the deploy — which is what makes it usable for testing. + * + * To point back at ToyOFT once its delegate is available, set OAPP_CONTRACT=ToyOFT (or change the + * default below). Nothing else needs editing. + */ +export const OAPP_CONTRACT = (process.env.OAPP_CONTRACT ?? '').trim() || 'MyOFT' diff --git a/package.json b/package.json index 8f8c9ae..2d96e71 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ "test:forge": "forge test", "test:hardhat": "hardhat test", "worker": "tsx worker/service.ts", - "cli": "tsx worker/cli.ts", "test:worker": "vitest run worker/test", "typecheck:worker": "tsc --noEmit -p worker/tsconfig.json", "docker:worker": "docker build -t compliance-dvn-worker worker/" @@ -96,7 +95,6 @@ "commander": "^15.0.0", "node-fetch": "^2.7.0", "pino": "^10.3.1", - "prom-client": "^15.1.3", - "zod": "^4.4.3" + "prom-client": "^15.1.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 10e5a6a..806a3c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,25 +24,22 @@ importers: prom-client: specifier: ^15.1.3 version: 15.1.3 - zod: - specifier: ^4.4.3 - version: 4.4.3 devDependencies: '@babel/core': specifier: ^7.23.9 version: 7.29.7 '@layerzerolabs/devtools': specifier: ~2.0.4 - version: 2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3))(@layerzerolabs/lz-definitions@3.1.3)(zod@4.4.3) + version: 2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76))(@layerzerolabs/lz-definitions@3.1.3)(zod@3.25.76) '@layerzerolabs/devtools-evm-hardhat': specifier: ^4.0.4 - version: 4.0.4(ovaobolztsg3eorfbocvjziauq) + version: 4.0.4(lb2hllwqwqr7h6pxhjygkhwati) '@layerzerolabs/eslint-config-next': specifier: ~2.3.39 version: 2.3.44(typescript@5.9.3) '@layerzerolabs/io-devtools': specifier: ~0.3.2 - version: 0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3) + version: 0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76) '@layerzerolabs/lz-definitions': specifier: ^3.0.148 version: 3.1.3 @@ -60,7 +57,7 @@ importers: version: 3.0.168 '@layerzerolabs/metadata-tools': specifier: ^3.0.0 - version: 3.0.3(@layerzerolabs/devtools-evm-hardhat@4.0.4(ovaobolztsg3eorfbocvjziauq))(@layerzerolabs/ua-devtools@5.0.2(bwxlrwzyswcvezarwvgodo4cmq)) + version: 3.0.3(@layerzerolabs/devtools-evm-hardhat@4.0.4(lb2hllwqwqr7h6pxhjygkhwati))(@layerzerolabs/ua-devtools@5.0.2(fqkslcceu5bqprnviet2czosty)) '@layerzerolabs/oapp-evm': specifier: ^0.4.1 version: 0.4.1(jrmbc75whvruccvgyfqpqmvol4) @@ -72,7 +69,7 @@ importers: version: 2.3.44 '@layerzerolabs/protocol-devtools-evm': specifier: ^5.0.2 - version: 5.0.3(k44onts5dsipnoch7nhiuxzbqm) + version: 5.0.3(ljkumtfuyrbiv243qszwmldjmq) '@layerzerolabs/script-devtools-evm-foundry': specifier: ^2.0.0 version: 2.0.0(7lsu4y4dammc5knpyul6zujjhq) @@ -5172,9 +5169,6 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - zod@4.4.3: - resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} - snapshots: '@adraffy/ens-normalize@1.11.1': {} @@ -5801,16 +5795,16 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@layerzerolabs/devtools-evm-hardhat@4.0.4(ovaobolztsg3eorfbocvjziauq)': + '@layerzerolabs/devtools-evm-hardhat@4.0.4(lb2hllwqwqr7h6pxhjygkhwati)': dependencies: '@ethersproject/abi': 5.8.0 '@ethersproject/abstract-signer': 5.8.0 '@ethersproject/contracts': 5.8.0 '@ethersproject/providers': 5.8.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) - '@layerzerolabs/devtools': 2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3))(@layerzerolabs/lz-definitions@3.1.3)(zod@4.4.3) - '@layerzerolabs/devtools-evm': 3.0.2(jdlumb4v4k572cwigiw37ribgm) + '@layerzerolabs/devtools': 2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76))(@layerzerolabs/lz-definitions@3.1.3)(zod@3.25.76) + '@layerzerolabs/devtools-evm': 3.0.2(7bkjhvfyyl63374wgxhqkukp34) '@layerzerolabs/export-deployments': 0.0.16 - '@layerzerolabs/io-devtools': 0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3) + '@layerzerolabs/io-devtools': 0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76) '@layerzerolabs/lz-definitions': 3.1.3 '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.8.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@18.19.130)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) '@safe-global/protocol-kit': 1.3.0(bufferutil@4.1.0)(ethers@5.8.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) @@ -5827,7 +5821,7 @@ snapshots: - supports-color - utf-8-validate - '@layerzerolabs/devtools-evm@3.0.2(jdlumb4v4k572cwigiw37ribgm)': + '@layerzerolabs/devtools-evm@3.0.2(7bkjhvfyyl63374wgxhqkukp34)': dependencies: '@ethersproject/abi': 5.8.0 '@ethersproject/abstract-provider': 5.8.0 @@ -5837,15 +5831,15 @@ snapshots: '@ethersproject/constants': 5.8.0 '@ethersproject/contracts': 5.8.0 '@ethersproject/providers': 5.8.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) - '@layerzerolabs/devtools': 2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3))(@layerzerolabs/lz-definitions@3.1.3)(zod@4.4.3) - '@layerzerolabs/io-devtools': 0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3) + '@layerzerolabs/devtools': 2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76))(@layerzerolabs/lz-definitions@3.1.3)(zod@3.25.76) + '@layerzerolabs/io-devtools': 0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76) '@layerzerolabs/lz-definitions': 3.1.3 '@safe-global/api-kit': 4.0.1(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) '@safe-global/protocol-kit': 1.3.0(bufferutil@4.1.0)(ethers@5.8.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) ethers: 5.8.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) fp-ts: 2.16.11 p-memoize: 4.0.4 - zod: 4.4.3 + zod: 3.25.76 transitivePeerDependencies: - bufferutil - encoding @@ -5853,26 +5847,16 @@ snapshots: - typescript - utf-8-validate - '@layerzerolabs/devtools@2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3))(@layerzerolabs/lz-definitions@3.1.3)(zod@3.25.76)': + '@layerzerolabs/devtools@2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76))(@layerzerolabs/lz-definitions@3.1.3)(zod@3.25.76)': dependencies: '@ethersproject/bytes': 5.8.0 - '@layerzerolabs/io-devtools': 0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3) + '@layerzerolabs/io-devtools': 0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76) '@layerzerolabs/lz-definitions': 3.1.3 bs58: 6.0.0 exponential-backoff: 3.1.3 js-yaml: 4.1.1 zod: 3.25.76 - '@layerzerolabs/devtools@2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3))(@layerzerolabs/lz-definitions@3.1.3)(zod@4.4.3)': - dependencies: - '@ethersproject/bytes': 5.8.0 - '@layerzerolabs/io-devtools': 0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3) - '@layerzerolabs/lz-definitions': 3.1.3 - bs58: 6.0.0 - exponential-backoff: 3.1.3 - js-yaml: 4.1.1 - zod: 4.4.3 - '@layerzerolabs/eslint-config-next@2.3.44(typescript@5.9.3)': dependencies: '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) @@ -5919,21 +5903,6 @@ snapshots: react: 17.0.2 yoga-layout-prebuilt: 1.10.0 - '@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3)': - dependencies: - chalk: 4.1.2 - logform: 2.7.0 - prompts: 2.4.2 - table: 6.8.2 - winston: 3.19.0 - zod: 4.4.3 - optionalDependencies: - ink: 3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10) - ink-gradient: 2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2) - ink-table: 3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2) - react: 17.0.2 - yoga-layout-prebuilt: 1.10.0 - '@layerzerolabs/lz-definitions@3.1.3': dependencies: tiny-invariant: 1.3.3 @@ -5992,10 +5961,10 @@ snapshots: bs58: 5.0.0 tiny-invariant: 1.3.3 - '@layerzerolabs/metadata-tools@3.0.3(@layerzerolabs/devtools-evm-hardhat@4.0.4(ovaobolztsg3eorfbocvjziauq))(@layerzerolabs/ua-devtools@5.0.2(bwxlrwzyswcvezarwvgodo4cmq))': + '@layerzerolabs/metadata-tools@3.0.3(@layerzerolabs/devtools-evm-hardhat@4.0.4(lb2hllwqwqr7h6pxhjygkhwati))(@layerzerolabs/ua-devtools@5.0.2(fqkslcceu5bqprnviet2czosty))': dependencies: - '@layerzerolabs/devtools-evm-hardhat': 4.0.4(ovaobolztsg3eorfbocvjziauq) - '@layerzerolabs/ua-devtools': 5.0.2(lfwrew4tkqhyuewztnmd346sz4) + '@layerzerolabs/devtools-evm-hardhat': 4.0.4(lb2hllwqwqr7h6pxhjygkhwati) + '@layerzerolabs/ua-devtools': 5.0.2(fqkslcceu5bqprnviet2czosty) '@layerzerolabs/oapp-evm@0.4.1(jrmbc75whvruccvgyfqpqmvol4)': dependencies: @@ -6024,23 +5993,7 @@ snapshots: prettier-plugin-packagejson: 2.5.22(prettier@3.8.4) prettier-plugin-solidity: 1.4.3(prettier@3.8.4) - '@layerzerolabs/protocol-devtools-evm@5.0.3(k44onts5dsipnoch7nhiuxzbqm)': - dependencies: - '@ethersproject/abstract-provider': 5.8.0 - '@ethersproject/abstract-signer': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/constants': 5.8.0 - '@ethersproject/contracts': 5.8.0 - '@ethersproject/providers': 5.8.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) - '@layerzerolabs/devtools': 2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3))(@layerzerolabs/lz-definitions@3.1.3)(zod@4.4.3) - '@layerzerolabs/devtools-evm': 3.0.2(jdlumb4v4k572cwigiw37ribgm) - '@layerzerolabs/io-devtools': 0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3) - '@layerzerolabs/lz-definitions': 3.1.3 - '@layerzerolabs/protocol-devtools': 3.0.2(@layerzerolabs/devtools@2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3))(@layerzerolabs/lz-definitions@3.1.3)(zod@4.4.3))(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3))(@layerzerolabs/lz-definitions@3.1.3)(zod@4.4.3) - p-memoize: 4.0.4 - zod: 4.4.3 - - '@layerzerolabs/protocol-devtools-evm@5.0.3(pc3syvcv36hybiqbq4ldbuord4)': + '@layerzerolabs/protocol-devtools-evm@5.0.3(ljkumtfuyrbiv243qszwmldjmq)': dependencies: '@ethersproject/abstract-provider': 5.8.0 '@ethersproject/abstract-signer': 5.8.0 @@ -6048,20 +6001,20 @@ snapshots: '@ethersproject/constants': 5.8.0 '@ethersproject/contracts': 5.8.0 '@ethersproject/providers': 5.8.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) - '@layerzerolabs/devtools': 2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3))(@layerzerolabs/lz-definitions@3.1.3)(zod@4.4.3) - '@layerzerolabs/devtools-evm': 3.0.2(jdlumb4v4k572cwigiw37ribgm) - '@layerzerolabs/io-devtools': 0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3) + '@layerzerolabs/devtools': 2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76))(@layerzerolabs/lz-definitions@3.1.3)(zod@3.25.76) + '@layerzerolabs/devtools-evm': 3.0.2(7bkjhvfyyl63374wgxhqkukp34) + '@layerzerolabs/io-devtools': 0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76) '@layerzerolabs/lz-definitions': 3.1.3 - '@layerzerolabs/protocol-devtools': 3.0.2(@layerzerolabs/devtools@2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3))(@layerzerolabs/lz-definitions@3.1.3)(zod@4.4.3))(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3))(@layerzerolabs/lz-definitions@3.1.3)(zod@4.4.3) + '@layerzerolabs/protocol-devtools': 3.0.2(@layerzerolabs/devtools@2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76))(@layerzerolabs/lz-definitions@3.1.3)(zod@3.25.76))(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76))(@layerzerolabs/lz-definitions@3.1.3)(zod@3.25.76) p-memoize: 4.0.4 zod: 3.25.76 - '@layerzerolabs/protocol-devtools@3.0.2(@layerzerolabs/devtools@2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3))(@layerzerolabs/lz-definitions@3.1.3)(zod@4.4.3))(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3))(@layerzerolabs/lz-definitions@3.1.3)(zod@4.4.3)': + '@layerzerolabs/protocol-devtools@3.0.2(@layerzerolabs/devtools@2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76))(@layerzerolabs/lz-definitions@3.1.3)(zod@3.25.76))(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76))(@layerzerolabs/lz-definitions@3.1.3)(zod@3.25.76)': dependencies: - '@layerzerolabs/devtools': 2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3))(@layerzerolabs/lz-definitions@3.1.3)(zod@4.4.3) - '@layerzerolabs/io-devtools': 0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3) + '@layerzerolabs/devtools': 2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76))(@layerzerolabs/lz-definitions@3.1.3)(zod@3.25.76) + '@layerzerolabs/io-devtools': 0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76) '@layerzerolabs/lz-definitions': 3.1.3 - zod: 4.4.3 + zod: 3.25.76 '@layerzerolabs/script-devtools-evm-foundry@2.0.0(7lsu4y4dammc5knpyul6zujjhq)': dependencies: @@ -6103,20 +6056,20 @@ snapshots: '@ethersproject/bytes': 5.8.0 '@ethersproject/contracts': 5.8.0 '@ethersproject/hash': 5.8.0 - '@layerzerolabs/devtools': 2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3))(@layerzerolabs/lz-definitions@3.1.3)(zod@3.25.76) - '@layerzerolabs/devtools-evm': 3.0.2(jdlumb4v4k572cwigiw37ribgm) - '@layerzerolabs/devtools-evm-hardhat': 4.0.4(ovaobolztsg3eorfbocvjziauq) + '@layerzerolabs/devtools': 2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76))(@layerzerolabs/lz-definitions@3.1.3)(zod@3.25.76) + '@layerzerolabs/devtools-evm': 3.0.2(7bkjhvfyyl63374wgxhqkukp34) + '@layerzerolabs/devtools-evm-hardhat': 4.0.4(lb2hllwqwqr7h6pxhjygkhwati) '@layerzerolabs/io-devtools': 0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76) '@layerzerolabs/lz-definitions': 3.1.3 '@layerzerolabs/lz-evm-sdk-v1': 3.1.3(bufferutil@4.1.0)(utf-8-validate@5.0.10) '@layerzerolabs/lz-evm-sdk-v2': 3.1.3(bufferutil@4.1.0)(utf-8-validate@5.0.10) '@layerzerolabs/lz-v2-utilities': 3.0.168 - '@layerzerolabs/protocol-devtools': 3.0.2(@layerzerolabs/devtools@2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3))(@layerzerolabs/lz-definitions@3.1.3)(zod@4.4.3))(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3))(@layerzerolabs/lz-definitions@3.1.3)(zod@4.4.3) - '@layerzerolabs/protocol-devtools-evm': 5.0.3(pc3syvcv36hybiqbq4ldbuord4) + '@layerzerolabs/protocol-devtools': 3.0.2(@layerzerolabs/devtools@2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76))(@layerzerolabs/lz-definitions@3.1.3)(zod@3.25.76))(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76))(@layerzerolabs/lz-definitions@3.1.3)(zod@3.25.76) + '@layerzerolabs/protocol-devtools-evm': 5.0.3(ljkumtfuyrbiv243qszwmldjmq) '@layerzerolabs/test-devtools-evm-hardhat': 0.5.3(hardhat@2.28.6(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@18.19.130)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))(solidity-bytes-utils@0.8.4) - '@layerzerolabs/ua-devtools': 5.0.2(lfwrew4tkqhyuewztnmd346sz4) - '@layerzerolabs/ua-devtools-evm': 7.0.1(dxxvtipscipcbnyv4szs4jgfby) - '@layerzerolabs/ua-devtools-evm-hardhat': 9.0.2(dkzkezjzdneq4ggl26xu2sk4iq) + '@layerzerolabs/ua-devtools': 5.0.2(fqkslcceu5bqprnviet2czosty) + '@layerzerolabs/ua-devtools-evm': 7.0.1(2awvyi7agm7juvjfkkamgdkwuy) + '@layerzerolabs/ua-devtools-evm-hardhat': 9.0.2(ziqk35g43wlktadin6holrpwiu) '@nomicfoundation/hardhat-ethers': 3.1.3(ethers@5.8.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@18.19.130)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) ethers: 5.8.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) fp-ts: 2.16.11 @@ -6143,50 +6096,50 @@ snapshots: - typescript - utf-8-validate - '@layerzerolabs/ua-devtools-evm-hardhat@9.0.2(dkzkezjzdneq4ggl26xu2sk4iq)': + '@layerzerolabs/ua-devtools-evm-hardhat@9.0.2(ziqk35g43wlktadin6holrpwiu)': dependencies: '@ethersproject/abi': 5.8.0 '@ethersproject/bytes': 5.8.0 '@ethersproject/contracts': 5.8.0 '@ethersproject/hash': 5.8.0 - '@layerzerolabs/devtools': 2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3))(@layerzerolabs/lz-definitions@3.1.3)(zod@4.4.3) - '@layerzerolabs/devtools-evm': 3.0.2(jdlumb4v4k572cwigiw37ribgm) - '@layerzerolabs/devtools-evm-hardhat': 4.0.4(ovaobolztsg3eorfbocvjziauq) - '@layerzerolabs/io-devtools': 0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3) + '@layerzerolabs/devtools': 2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76))(@layerzerolabs/lz-definitions@3.1.3)(zod@3.25.76) + '@layerzerolabs/devtools-evm': 3.0.2(7bkjhvfyyl63374wgxhqkukp34) + '@layerzerolabs/devtools-evm-hardhat': 4.0.4(lb2hllwqwqr7h6pxhjygkhwati) + '@layerzerolabs/io-devtools': 0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76) '@layerzerolabs/lz-definitions': 3.1.3 - '@layerzerolabs/protocol-devtools': 3.0.2(@layerzerolabs/devtools@2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3))(@layerzerolabs/lz-definitions@3.1.3)(zod@4.4.3))(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3))(@layerzerolabs/lz-definitions@3.1.3)(zod@4.4.3) - '@layerzerolabs/protocol-devtools-evm': 5.0.3(k44onts5dsipnoch7nhiuxzbqm) - '@layerzerolabs/ua-devtools': 5.0.2(lfwrew4tkqhyuewztnmd346sz4) - '@layerzerolabs/ua-devtools-evm': 7.0.1(dxxvtipscipcbnyv4szs4jgfby) + '@layerzerolabs/protocol-devtools': 3.0.2(@layerzerolabs/devtools@2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76))(@layerzerolabs/lz-definitions@3.1.3)(zod@3.25.76))(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76))(@layerzerolabs/lz-definitions@3.1.3)(zod@3.25.76) + '@layerzerolabs/protocol-devtools-evm': 5.0.3(ljkumtfuyrbiv243qszwmldjmq) + '@layerzerolabs/ua-devtools': 5.0.2(fqkslcceu5bqprnviet2czosty) + '@layerzerolabs/ua-devtools-evm': 7.0.1(2awvyi7agm7juvjfkkamgdkwuy) ethers: 5.8.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) hardhat: 2.28.6(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@18.19.130)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) hardhat-deploy: 0.12.4(bufferutil@4.1.0)(utf-8-validate@5.0.10) p-memoize: 4.0.4 typescript: 5.9.3 - '@layerzerolabs/ua-devtools-evm@7.0.1(dxxvtipscipcbnyv4szs4jgfby)': + '@layerzerolabs/ua-devtools-evm@7.0.1(2awvyi7agm7juvjfkkamgdkwuy)': dependencies: '@ethersproject/constants': 5.8.0 '@ethersproject/contracts': 5.8.0 - '@layerzerolabs/devtools': 2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3))(@layerzerolabs/lz-definitions@3.1.3)(zod@4.4.3) - '@layerzerolabs/devtools-evm': 3.0.2(jdlumb4v4k572cwigiw37ribgm) - '@layerzerolabs/io-devtools': 0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3) + '@layerzerolabs/devtools': 2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76))(@layerzerolabs/lz-definitions@3.1.3)(zod@3.25.76) + '@layerzerolabs/devtools-evm': 3.0.2(7bkjhvfyyl63374wgxhqkukp34) + '@layerzerolabs/io-devtools': 0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76) '@layerzerolabs/lz-definitions': 3.1.3 '@layerzerolabs/lz-v2-utilities': 3.0.168 - '@layerzerolabs/protocol-devtools': 3.0.2(@layerzerolabs/devtools@2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3))(@layerzerolabs/lz-definitions@3.1.3)(zod@4.4.3))(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3))(@layerzerolabs/lz-definitions@3.1.3)(zod@4.4.3) - '@layerzerolabs/protocol-devtools-evm': 5.0.3(k44onts5dsipnoch7nhiuxzbqm) - '@layerzerolabs/ua-devtools': 5.0.2(lfwrew4tkqhyuewztnmd346sz4) + '@layerzerolabs/protocol-devtools': 3.0.2(@layerzerolabs/devtools@2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76))(@layerzerolabs/lz-definitions@3.1.3)(zod@3.25.76))(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76))(@layerzerolabs/lz-definitions@3.1.3)(zod@3.25.76) + '@layerzerolabs/protocol-devtools-evm': 5.0.3(ljkumtfuyrbiv243qszwmldjmq) + '@layerzerolabs/ua-devtools': 5.0.2(fqkslcceu5bqprnviet2czosty) p-memoize: 4.0.4 - zod: 4.4.3 + zod: 3.25.76 - '@layerzerolabs/ua-devtools@5.0.2(lfwrew4tkqhyuewztnmd346sz4)': + '@layerzerolabs/ua-devtools@5.0.2(fqkslcceu5bqprnviet2czosty)': dependencies: - '@layerzerolabs/devtools': 2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3))(@layerzerolabs/lz-definitions@3.1.3)(zod@4.4.3) - '@layerzerolabs/io-devtools': 0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3) + '@layerzerolabs/devtools': 2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76))(@layerzerolabs/lz-definitions@3.1.3)(zod@3.25.76) + '@layerzerolabs/io-devtools': 0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76) '@layerzerolabs/lz-definitions': 3.1.3 '@layerzerolabs/lz-v2-utilities': 3.0.168 - '@layerzerolabs/protocol-devtools': 3.0.2(@layerzerolabs/devtools@2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3))(@layerzerolabs/lz-definitions@3.1.3)(zod@4.4.3))(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@4.4.3))(@layerzerolabs/lz-definitions@3.1.3)(zod@4.4.3) - zod: 4.4.3 + '@layerzerolabs/protocol-devtools': 3.0.2(@layerzerolabs/devtools@2.0.5(@ethersproject/bytes@5.8.0)(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76))(@layerzerolabs/lz-definitions@3.1.3)(zod@3.25.76))(@layerzerolabs/io-devtools@0.3.2(ink-gradient@2.0.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink-table@3.1.0(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2))(ink@3.2.0(bufferutil@4.1.0)(react@17.0.2)(utf-8-validate@5.0.10))(react@17.0.2)(yoga-layout-prebuilt@1.10.0)(zod@3.25.76))(@layerzerolabs/lz-definitions@3.1.3)(zod@3.25.76) + zod: 3.25.76 '@mdn/browser-compat-data@5.7.6': {} @@ -11120,5 +11073,3 @@ snapshots: ethers: 5.8.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) zod@3.25.76: {} - - zod@4.4.3: {} diff --git a/tasks/demo.ts b/tasks/demo.ts deleted file mode 100644 index 4e8cdfa..0000000 --- a/tasks/demo.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { task } from 'hardhat/config' - -const TOY_OFT = '0xdEc1591D39ECb8278d1a2256a5BF17507A375F00' -const CLEAN_RECIPIENT = '0x000000000000000000000000000000000000cCCc' -const ERC20_ABI = ['function balanceOf(address) view returns (uint256)'] - -task('demo:clean', 'Send ToyOFT to a CLEAN (non-flagged) recipient — expect DELIVERED') - .addOptionalParam('to', 'clean recipient address', CLEAN_RECIPIENT) - .addOptionalParam('dst', 'destination: base|opt', 'base') - .addOptionalParam('amount', 'human amount', '1') - .setAction(async (args, hre) => { - console.log('\n=== CLEAN TRANSFER (expect DELIVERED) ===\n') - await hre.run('demo:send', { to: args.to, dst: args.dst, amount: args.amount }) - }) - -task('demo:veto', 'Send ToyOFT to the FLAGGED recipient — expect VETO (never delivered)') - .addOptionalParam('dst', 'destination: base|opt', 'base') - .addOptionalParam('amount', 'human amount', '1') - .setAction(async (args, hre) => { - const flagged = process.env.TEST_DENYLIST - if (!flagged || flagged.trim() === '') { - throw new Error('set TEST_DENYLIST in .env to a flagged recipient address') - } - console.log('\n=== FLAGGED TRANSFER (expect VETO -> never delivered) ===\n') - console.log('Flagged recipient:', flagged) - await hre.run('demo:send', { to: flagged, dst: args.dst, amount: args.amount }) - }) - -task('demo:show', 'Read-only: show ToyOFT balances for clean + flagged recipients on destination chain') - .addOptionalParam('dst', 'destination: base|opt', 'base') - .setAction(async (args, hre) => { - const flagged = process.env.TEST_DENYLIST || '' - - const rpc = - args.dst === 'opt' - ? process.env.RPC_URL_OPTIMISM_SEPOLIA || 'https://sepolia.optimism.io' - : process.env.RPC_URL_BASE_SEPOLIA || 'https://sepolia.base.org' - - const provider = new hre.ethers.providers.JsonRpcProvider(rpc) - const token = new hre.ethers.Contract(TOY_OFT, ERC20_ABI, provider) - - const cleanBal = await token.balanceOf(CLEAN_RECIPIENT) - const cleanFmt = hre.ethers.utils.formatEther(cleanBal) - - const network = args.dst === 'opt' ? 'Optimism Sepolia' : 'Base Sepolia' - console.log(`\n=== ToyOFT balances on ${network} ===\n`) - console.log(`CLEAN ${CLEAN_RECIPIENT} : ${cleanFmt} TOY (delivered)`) - - if (flagged) { - const flaggedBal = await token.balanceOf(flagged) - const flaggedFmt = hre.ethers.utils.formatEther(flaggedBal) - console.log(`FLAGGED ${flagged} : ${flaggedFmt} TOY (vetoed, never delivered)`) - } else { - console.log('FLAGGED — set TEST_DENYLIST in .env to see the vetoed balance') - } - console.log() - }) diff --git a/tasks/demoSend.ts b/tasks/demoSend.ts deleted file mode 100644 index 9eeb549..0000000 --- a/tasks/demoSend.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { task } from 'hardhat/config' - -import { EndpointId } from '@layerzerolabs/lz-definitions' - -task('demo:send', 'Send ToyOFT from current network to the other testnet') - .addParam('to', 'recipient address on destination') - .addOptionalParam('dst', 'destination: base|opt', 'base') - .addOptionalParam('amount', 'human amount', '1') - .setAction(async (args, hre) => { - const dstEid = args.dst === 'opt' ? EndpointId.OPTSEP_V2_TESTNET : EndpointId.BASESEP_V2_TESTNET - const { deployer } = await hre.getNamedAccounts() - const d = await hre.deployments.get('ToyOFT') - const oft = await hre.ethers.getContractAt('ToyOFT', d.address) - - const amount = hre.ethers.utils.parseEther(args.amount) - await (await oft.mint(deployer, amount)).wait() - - const to = hre.ethers.utils.hexZeroPad(args.to, 32) - // Build executor options (lzReceive gas). Use the project's options utility. - const { Options } = await import('@layerzerolabs/lz-v2-utilities') - const options = Options.newOptions().addExecutorLzReceiveOption(200000, 0).toHex() - - const sendParam = { - dstEid, - to, - amountLD: amount, - minAmountLD: amount, - extraOptions: options, - composeMsg: '0x', - oftCmd: '0x', - } - const fee = await oft.quoteSend(sendParam, false) - const tx = await oft.send(sendParam, fee, deployer, { value: fee.nativeFee }) - const receipt = await tx.wait() - console.log('sent tx:', receipt.transactionHash) - console.log('scan:', `https://testnet.layerzeroscan.com/tx/${receipt.transactionHash}`) - }) diff --git a/tasks/index.ts b/tasks/index.ts index c237fcc..50cee81 100644 --- a/tasks/index.ts +++ b/tasks/index.ts @@ -1,6 +1,6 @@ import './configureDvn' -import './demo' -import './demoSend' +import './preflight' +import './verifyWiring' import './sendOFT' import './simple-workers-mock/commit' import './simple-workers-mock/commitAndExecute' diff --git a/tasks/preflight.ts b/tasks/preflight.ts new file mode 100644 index 0000000..54c95c6 --- /dev/null +++ b/tasks/preflight.ts @@ -0,0 +1,272 @@ +import { existsSync, readFileSync } from 'fs' +import path from 'path' + +import { task } from 'hardhat/config' + +import { OAPP_CONTRACT } from '../oapp.contract' + +const EVM_ADDRESS = /^0x[0-9a-fA-F]{40}$/ +/** 0x is optional — ethers and hardhat both accept a bare 64-hex key. */ +const HEX_PRIVATE_KEY = /^(0x)?[0-9a-fA-F]{64}$/ + +/** ReceiveUln302 per LayerZero eid — must match deploy/ComplianceDVN.ts. */ +const RECEIVE_ULN: Record = { + 40245: '0x12523de19dc41c91F7d2093E0CFbB76b17012C8d', // base-sepolia + 40232: '0x9284fd59B95b9143AF0b9795CAC16eb3C723C9Ca', // optimism-sepolia +} + +/** EndpointV2, identical on both testnets. */ +const ENDPOINT_V2 = '0x6EDCE65403992e310A62460808c4b910D972f10f' + +const DVN_ENV: Record = { + 40245: 'DVN_BASE_SEPOLIA', + 40232: 'DVN_OPTIMISM_SEPOLIA', +} + +/** + * How many deploy-equivalents of balance to ask for. Deployment is one transaction; wiring adds + * several config calls of comparable size, and gas can move between now and then. + */ +const DEPLOY_COST_HEADROOM = 25 + +/** + * Price the actual deployment instead of guessing at it. + * + * Returns undefined when estimation is not possible (an unfunded account on a strict node, an + * unknown chain), in which case the caller simply skips the adequacy check rather than inventing + * a number — the separate zero-balance check still blocks the genuinely broken case. + */ +async function estimateDeployCost( + hre: import('hardhat/types').HardhatRuntimeEnvironment, + deployer: string, + eid: number | undefined +): Promise< + { gas: import('ethers').BigNumber; price: import('ethers').BigNumber; cost: import('ethers').BigNumber } | undefined +> { + const receiveUln = eid ? RECEIVE_ULN[eid] : undefined + if (!receiveUln) return undefined + try { + const factory = await hre.ethers.getContractFactory('ComplianceDVN') + const tx = factory.getDeployTransaction( + deployer, + (process.env.OPERATOR_ADDRESS ?? '').trim() || deployer, + receiveUln, + hre.ethers.utils.parseEther('0.00005') + ) + const [gas, price] = await Promise.all([ + hre.ethers.provider.estimateGas({ ...tx, from: deployer }), + hre.ethers.provider.getGasPrice(), + ]) + return { gas, price, cost: gas.mul(price) } + } catch { + return undefined + } +} + +/** + * Check everything that has to be true before spending gas, and report every problem at once. + * + * Deployment failures are cheap to diagnose but expensive to half-complete: a deploy that lands + * and then cannot be wired leaves an OApp pointing at nothing. This runs the same checks the + * deploy and wire steps depend on, without sending a transaction. + */ +task('dvn:preflight', 'Validate deploy prerequisites on the current --network without sending transactions').setAction( + async (_args, hre) => { + const problems: string[] = [] + const warnings: string[] = [] + const eid = (hre.network.config as { eid?: number }).eid + + console.log(`network: ${hre.network.name}${eid ? ` (eid ${eid})` : ''}`) + + // --- signer ------------------------------------------------------------------------- + // + // Kept deliberately minimal: hardhat validates `accounts` while loading the config, so a + // malformed key fails with its own clear message ("private key too short, expected 32 + // bytes") before this task ever runs. The only gap worth covering is an ABSENT key, which + // hardhat tolerates at load time and then fails on much later and much less clearly. + // + // The 0x prefix is optional because ethers and hardhat both accept a bare 64-hex key. + // Requiring it here would block a configuration that deploys perfectly well. + const pk = (process.env.PRIVATE_KEY ?? '').trim() + if (!pk) problems.push('PRIVATE_KEY is not set — hardhat has no account to deploy from') + else if (!HEX_PRIVATE_KEY.test(pk)) { + problems.push( + `PRIVATE_KEY is not a 32-byte hex key (got ${pk.length} chars; expected 64 hex digits, with or without a 0x prefix)` + ) + } + + let deployer: string | undefined + if (!problems.length) { + try { + deployer = (await hre.getNamedAccounts()).deployer + } catch (err) { + problems.push(`cannot resolve the deployer account: ${(err as Error).message}`) + } + } + + // --- rpc + balance ------------------------------------------------------------------ + if (deployer) { + console.log(`deployer: ${deployer}`) + try { + const balance = await hre.ethers.provider.getBalance(deployer) + console.log(`balance: ${hre.ethers.utils.formatEther(balance)} ETH`) + if (balance.isZero()) { + problems.push(`deployer has no balance on ${hre.network.name} — fund it first`) + } else { + // Compare against the MEASURED deploy cost rather than a fixed ETH figure. A + // hardcoded threshold calibrated for L1 is off by three orders of magnitude on + // an OP-stack L2, so it fires on every healthy balance — and a warning that + // always fires is one people learn to skip. + const estimate = await estimateDeployCost(hre, deployer, eid) + if (estimate) { + const { gas, price, cost } = estimate + console.log( + `deploy est: ${hre.ethers.utils.formatEther(cost)} ETH ` + + `(${gas.toString()} gas @ ${hre.ethers.utils.formatUnits(price, 'gwei')} gwei)` + ) + // Deploy plus wiring is a handful of similar transactions; ask for real + // headroom on top so gas moving under us does not strand it half-done. + const needed = cost.mul(DEPLOY_COST_HEADROOM) + if (balance.lt(needed)) { + warnings.push( + `deployer balance ${hre.ethers.utils.formatEther(balance)} ETH is under ${DEPLOY_COST_HEADROOM}x the estimated deploy cost (${hre.ethers.utils.formatEther(needed)} ETH). Deploy plus wiring is several transactions — top it up rather than risk stopping midway.` + ) + } + } + } + } catch (err) { + problems.push(`RPC unreachable for ${hre.network.name}: ${(err as Error).message}`) + } + } + + // --- chain metadata ----------------------------------------------------------------- + if (!eid) problems.push(`network '${hre.network.name}' has no eid configured`) + else if (!RECEIVE_ULN[eid]) problems.push(`no ReceiveUln302 known for eid ${eid}`) + else console.log(`receiveUln: ${RECEIVE_ULN[eid]}`) + + // --- operator separation ------------------------------------------------------------ + const operator = (process.env.OPERATOR_ADDRESS ?? '').trim() + if (operator && !EVM_ADDRESS.test(operator)) { + problems.push('OPERATOR_ADDRESS is set but is not a 20-byte EVM address') + } else if (!operator) { + warnings.push( + 'OPERATOR_ADDRESS is unset, so owner and operator collapse onto the deployer. The worker would then be able to approve its own held packets — set it to the worker key to keep approval a human-only action.' + ) + } else if (!deployer) { + // Never claim the roles are separate without having compared them. The owner/operator + // split is the reason approval is human-only; asserting it unverified is worse than + // saying nothing, because it reads as a passed check. + console.log(`operator: ${operator} (cannot compare to owner until PRIVATE_KEY is valid)`) + } else if (operator.toLowerCase() === deployer.toLowerCase()) { + warnings.push('OPERATOR_ADDRESS equals the deployer, so owner and operator are the same key') + } else { + console.log(`operator: ${operator} (distinct from owner — good)`) + } + + // The operator's balance is not needed to deploy, but the worker cannot verify a single + // packet without it — and that failure surfaces much later, as `submitVerification failed`, + // long after deployment looked successful. Cheaper to say so now. + if (operator && EVM_ADDRESS.test(operator)) { + try { + const balance = await hre.ethers.provider.getBalance(operator) + console.log(`operator balance: ${hre.ethers.utils.formatEther(balance)} ETH`) + if (balance.isZero()) { + warnings.push( + `operator ${operator} has no balance on ${hre.network.name}. It is not needed to deploy, but the worker signs submitVerification/commitVerification on the DESTINATION chain — so it needs funding on every chain before it can verify anything.` + ) + } + } catch { + // The deployer balance check above already reports an unreachable RPC. + } + } + + // --- already deployed? -------------------------------------------------------------- + try { + const existing = await hre.deployments.getOrNull('ComplianceDVN') + if (existing) { + console.log(`existing deployment: ${existing.address}`) + const dvn = await hre.ethers.getContractAt('ComplianceDVN', existing.address) + // A deployment that predates the RiskVerdict work has no ACTION_BLOCK constant, so + // the worker's ABI would not match it. + try { + await dvn.ACTION_BLOCK() + console.log(' exposes ACTION_BLOCK — verdict-event ABI present') + } catch { + warnings.push( + `the existing deployment at ${existing.address} predates the RiskVerdict interface; redeploy and re-wire, then update DVN_* in .env` + ) + } + // A deployment that predates the assignJob gate accepts jobs from anyone, which + // lets a stranger point the worker at packets no one asked it to verify. + try { + await dvn.sendUln() + console.log(' exposes sendUln — assignJob is gated to the send library') + } catch { + warnings.push( + `the existing deployment at ${existing.address} predates the assignJob send-library gate; redeploy and re-wire, then update DVN_* in .env` + ) + } + } else { + console.log('existing deployment: none (this will be a fresh deploy)') + } + } catch (err) { + warnings.push(`could not inspect existing deployments: ${(err as Error).message}`) + } + + // --- wiring authority --------------------------------------------------------------- + // + // Wiring calls `EndpointV2.setConfig(oapp, lib, params)`, which only the OApp or its + // registered delegate may do. Getting this wrong does not fail at config time — it fails + // as `LZ_Unauthorized()` mid-wire, after the deploy has already landed. Read it up front. + if (deployer) { + const record = path.join(__dirname, '..', 'deployments', hre.network.name, `${OAPP_CONTRACT}.json`) + if (!existsSync(record)) { + warnings.push( + `no ${OAPP_CONTRACT} deployment for ${hre.network.name} — deploy it before wiring (the OApp must exist to be configured)` + ) + } else { + const oapp = (JSON.parse(readFileSync(record, 'utf8')) as { address: string }).address + try { + const endpoint = new hre.ethers.Contract( + ENDPOINT_V2, + ['function delegates(address) view returns (address)'], + hre.ethers.provider + ) + const delegate: string = await endpoint.delegates(oapp) + const authorized = delegate.toLowerCase() === deployer.toLowerCase() + console.log(`oapp: ${OAPP_CONTRACT} ${oapp}`) + console.log(` delegate ${delegate} ${authorized ? 'is us — can wire' : 'is NOT us'}`) + if (!authorized) { + problems.push( + `${OAPP_CONTRACT} at ${oapp} has delegate ${delegate}, not the deployer ${deployer}. Wiring would revert with LZ_Unauthorized(). Either wire with that key, have it call setDelegate(${deployer}), or point OAPP_CONTRACT at an OApp this key owns.` + ) + } + } catch (err) { + warnings.push(`could not read the OApp delegate: ${(err as Error).message}`) + } + } + } + + // --- wiring prerequisite ------------------------------------------------------------ + if (eid && DVN_ENV[eid]) { + const configured = (process.env[DVN_ENV[eid]] ?? '').trim() + if (!EVM_ADDRESS.test(configured)) { + console.log(`${DVN_ENV[eid]}: not set (expected — set it after deploying, before wiring)`) + } else { + console.log(`${DVN_ENV[eid]}: ${configured}`) + } + } + + // --- report ------------------------------------------------------------------------- + console.log('') + for (const w of warnings) console.log(`WARN ${w}`) + for (const p of problems) console.log(`ERROR ${p}`) + if (problems.length) { + // Exit rather than throw: a failed preflight is an expected outcome, and a stack trace + // buries the list of problems the operator actually needs to read. + console.log(`\n${problems.length} blocking problem(s) — resolve them before deploying`) + process.exit(1) + } + console.log(warnings.length ? `preflight passed with ${warnings.length} warning(s)` : 'preflight passed') + } +) diff --git a/tasks/verifyWiring.ts b/tasks/verifyWiring.ts new file mode 100644 index 0000000..a8440db --- /dev/null +++ b/tasks/verifyWiring.ts @@ -0,0 +1,99 @@ +import { existsSync, readFileSync } from 'fs' +import path from 'path' + +import { task } from 'hardhat/config' + +import { OAPP_CONTRACT } from '../oapp.contract' + +const ENDPOINT_V2 = '0x6EDCE65403992e310A62460808c4b910D972f10f' +const ULN_CONFIG_TYPE = 2 + +/** Send and receive libraries per eid, matching the LayerZero deployment. */ +const LIBS: Record = { + 40245: { + sendUln: '0xC1868e054425D378095A003EcbA3823a5D0135C9', + receiveUln: '0x12523de19dc41c91F7d2093E0CFbB76b17012C8d', + peerEid: 40232, + }, + 40232: { + sendUln: '0xB31D2cb502E25B30C651842C7C3293c51Fe6d16f', + receiveUln: '0x9284fd59B95b9143AF0b9795CAC16eb3C723C9Ca', + peerEid: 40245, + }, +} + +const ENDPOINT_ABI = [ + 'function getConfig(address _oapp, address _lib, uint32 _eid, uint32 _configType) view returns (bytes)', +] + +const ULN_CONFIG_TUPLE = + 'tuple(uint64 confirmations, uint8 requiredDVNCount, uint8 optionalDVNCount, uint8 optionalDVNThreshold, address[] requiredDVNs, address[] optionalDVNs)' + +/** + * Confirm the DVN is actually REQUIRED by the wired pathway, by decoding the on-chain ULN config. + * + * `lz:oapp:wire --dry-run` reporting "no action necessary" says the chain matches the config file; + * it does not say the config file expresses what we think. This reads the ULN config back and + * asserts our DVN is in `requiredDVNs` — which is the single fact the whole veto mechanism rests + * on. If the DVN were merely optional, or absent, withholding verification would block nothing. + */ +task('dvn:verify-wiring', 'Decode the on-chain ULN config and assert our DVN is required').setAction( + async (_args, hre) => { + const eid = (hre.network.config as { eid?: number }).eid + if (!eid || !LIBS[eid]) throw new Error(`no library set known for eid ${eid}`) + const { sendUln, receiveUln, peerEid } = LIBS[eid] + + const record = path.join(__dirname, '..', 'deployments', hre.network.name, `${OAPP_CONTRACT}.json`) + if (!existsSync(record)) throw new Error(`no ${OAPP_CONTRACT} deployment for ${hre.network.name}`) + const oapp = (JSON.parse(readFileSync(record, 'utf8')) as { address: string }).address + + const dvnRecord = path.join(__dirname, '..', 'deployments', hre.network.name, 'ComplianceDVN.json') + if (!existsSync(dvnRecord)) throw new Error(`no ComplianceDVN deployment for ${hre.network.name}`) + const dvn = (JSON.parse(readFileSync(dvnRecord, 'utf8')) as { address: string }).address.toLowerCase() + + const endpoint = new hre.ethers.Contract(ENDPOINT_V2, ENDPOINT_ABI, hre.ethers.provider) + const fails: string[] = [] + + console.log(`network: ${hre.network.name} (eid ${eid})`) + console.log(`oapp: ${OAPP_CONTRACT} ${oapp}`) + console.log(`dvn: ${dvn}`) + + // Both directions are configured on THIS chain: the send config governs outbound messages, + // the receive config governs what this chain demands of inbound ones. A DVN missing from + // either leaves that direction unscreened. + for (const [label, lib] of [ + ['send ', sendUln], + ['receive', receiveUln], + ] as const) { + const raw = await endpoint.getConfig(oapp, lib, peerEid, ULN_CONFIG_TYPE) + const [cfg] = hre.ethers.utils.defaultAbiCoder.decode([ULN_CONFIG_TUPLE], raw) + const required = (cfg.requiredDVNs as string[]).map((a) => a.toLowerCase()) + const has = required.includes(dvn) + console.log( + ` ${label}: confirmations=${cfg.confirmations} requiredDVNCount=${cfg.requiredDVNCount} ` + + `optional=${cfg.optionalDVNCount}/${cfg.optionalDVNThreshold}` + ) + console.log( + ` requiredDVNs=${JSON.stringify(required)} ${has ? 'contains our DVN' : 'MISSING our DVN'}` + ) + + if (!has) + fails.push( + `${label.trim()} config does not require our DVN — withholding verification would block nothing` + ) + if (Number(cfg.requiredDVNCount) === 0) fails.push(`${label.trim()} config requires zero DVNs`) + // An optional-DVN threshold of 0 alongside optional DVNs would let a message settle + // without any of them; not our configuration, but worth catching if it ever becomes so. + if (Number(cfg.optionalDVNCount) > 0 && Number(cfg.optionalDVNThreshold) === 0) { + fails.push(`${label.trim()} config has optional DVNs with a zero threshold`) + } + } + + console.log('') + if (fails.length) { + for (const f of fails) console.log(`FAIL ${f}`) + process.exit(1) + } + console.log('our DVN is required in both directions — the veto path is armed') + } +) diff --git a/test/foundry/ComplianceDVN.t.sol b/test/foundry/ComplianceDVN.t.sol index ca479e9..47ad979 100644 --- a/test/foundry/ComplianceDVN.t.sol +++ b/test/foundry/ComplianceDVN.t.sol @@ -23,7 +23,8 @@ contract ComplianceDVNTest is Test { address receiveUln = address(0xCAFE); function setUp() public { - dvn = new ComplianceDVN(address(this), operator, receiveUln, 0.0001 ether); + // sendUln = this test contract, so the assignJob tests below can call it directly. + dvn = new ComplianceDVN(address(this), operator, address(this), receiveUln, 0.0001 ether); } function test_getFee_returnsConfiguredFee() public view { @@ -34,19 +35,69 @@ contract ComplianceDVNTest is Test { function test_submitVerification_onlyOperator() public { vm.prank(address(0xDEAD)); vm.expectRevert(ComplianceDVN.NotOperator.selector); - dvn.submitVerification(hex"01", keccak256("p"), 5); + dvn.submitVerification(hex"01", keccak256("p"), 5, 0, 0, 0, bytes32(0)); } - function test_submitVerification_forwardsToReceiveUln() public { + function test_submitVerification_forwardsToReceiveUln_andEmitsVerdict() public { MockReceiveUln mock = new MockReceiveUln(); - ComplianceDVN d = new ComplianceDVN(address(this), operator, address(mock), 0); + ComplianceDVN d = new ComplianceDVN(address(this), operator, address(this), address(mock), 0); + vm.expectEmit(true, false, false, true); + emit ComplianceDVN.RiskVerdict(keccak256("p"), 0, 12, 5, keccak256("ev")); vm.prank(operator); - d.submitVerification(hex"0102", keccak256("p"), 7); + d.submitVerification(hex"0102", keccak256("p"), 7, 0, 12, 5, keccak256("ev")); assertEq(mock.calls(), 1); assertEq(mock.lastPayloadHash(), keccak256("p")); assertEq(mock.lastConfirmations(), 7); } + /// A packet that was blocked or held cannot also have been verified — the audit trail must + /// not be able to contradict itself. + function test_submitVerification_rejectsNonAllowAction() public { + MockReceiveUln mock = new MockReceiveUln(); + ComplianceDVN d = new ComplianceDVN(address(this), operator, address(this), address(mock), 0); + for (uint8 action = 1; action <= 3; action++) { + vm.prank(operator); + vm.expectRevert(abi.encodeWithSelector(ComplianceDVN.VerificationRequiresAllow.selector, action)); + d.submitVerification(hex"01", keccak256("p"), 5, action, 0, 0, bytes32(0)); + } + assertEq(mock.calls(), 0, "no verification may have reached the ULN"); + } + + function test_recordVerdict_emitsForOperator() public { + vm.expectEmit(true, false, false, true); + emit ComplianceDVN.RiskVerdict(keccak256("p"), 3, 100, 1, keccak256("ev")); + vm.prank(operator); + dvn.recordVerdict(keccak256("p"), 3, 100, 1, keccak256("ev")); + } + + function test_recordVerdict_onlyOperator() public { + vm.prank(address(0xDEAD)); + vm.expectRevert(ComplianceDVN.NotOperator.selector); + dvn.recordVerdict(keccak256("p"), 3, 100, 1, bytes32(0)); + } + + /// An allow rides along on submitVerification, so recording one separately would + /// double-report the same outcome. + function test_recordVerdict_rejectsAllow() public { + vm.prank(operator); + vm.expectRevert(ComplianceDVN.AllowNotSeparatelyRecorded.selector); + dvn.recordVerdict(keccak256("p"), 0, 0, 0, bytes32(0)); + } + + function test_recordVerdict_rejectsUnknownAction() public { + vm.prank(operator); + vm.expectRevert(abi.encodeWithSelector(ComplianceDVN.UnknownAction.selector, uint8(4))); + dvn.recordVerdict(keccak256("p"), 4, 0, 0, bytes32(0)); + } + + /// These codes are part of the event ABI; an indexer decoding old logs depends on them. + function test_actionCodes_arePinned() public view { + assertEq(dvn.ACTION_ALLOW(), 0); + assertEq(dvn.ACTION_DELAY(), 1); + assertEq(dvn.ACTION_MANUAL_REVIEW(), 2); + assertEq(dvn.ACTION_BLOCK(), 3); + } + function test_assignJob_returnsFee_andEmits() public { ILayerZeroDVN.AssignJobParam memory p = ILayerZeroDVN.AssignJobParam({ dstEid: 40245, @@ -61,9 +112,31 @@ contract ComplianceDVNTest is Test { assertEq(ret, 0.0001 ether); } + function test_approvePacket_emitsForOwner() public { + vm.expectEmit(true, false, false, true); + emit ComplianceDVN.PacketApproved(keccak256("p"), address(this)); + dvn.approvePacket(keccak256("p")); + } + + // Approval is a human override of a risk verdict, so the operator key the worker holds + // must NOT be able to release the packets that worker chose to withhold. + function test_approvePacket_rejectsOperator() public { + vm.prank(operator); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, operator)); + dvn.approvePacket(keccak256("p")); + } + + function test_approvePacket_rejectsStranger() public { + vm.prank(address(0xDEAD)); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, address(0xDEAD))); + dvn.approvePacket(keccak256("p")); + } + function test_setters_onlyOwner() public { dvn.setOperator(address(0xAAA)); assertEq(dvn.operator(), address(0xAAA)); + dvn.setSendUln(address(0xCCC)); + assertEq(dvn.sendUln(), address(0xCCC)); dvn.setReceiveUln(address(0xBBB)); assertEq(dvn.receiveUln(), address(0xBBB)); dvn.setFee(123); @@ -101,5 +174,20 @@ contract ComplianceDVNTest is Test { assertEq(ret, 0.0001 ether); } + // The worker treats a JobAssigned payloadHash as "ours to screen" and spends operator gas + // verifying it, so anyone able to assign jobs could point the worker at foreign packets. + function test_assignJob_rejectsNonSendLibrary() public { + ILayerZeroDVN.AssignJobParam memory p = ILayerZeroDVN.AssignJobParam({ + dstEid: 40245, + packetHeader: hex"01", + payloadHash: keccak256("payload"), + confirmations: 5, + sender: address(0x1234) + }); + vm.prank(address(0xDEAD)); + vm.expectRevert(ComplianceDVN.NotSendLibrary.selector); + dvn.assignJob(p, ""); + } + receive() external payable {} } diff --git a/test/foundry/ComplianceDvnVeto.t.sol b/test/foundry/ComplianceDvnVeto.t.sol index 114cc53..2759b9c 100644 --- a/test/foundry/ComplianceDvnVeto.t.sol +++ b/test/foundry/ComplianceDvnVeto.t.sol @@ -93,8 +93,10 @@ contract ComplianceDvnVetoTest is TestHelperOz5 { address recvUlnB = endpointSetup.receiveLibs[1]; // eid 2 // Deploy the ComplianceDVN for endpoint B. operator = this test, fee = 0 - // (the harness does not forward msg.value to assignJob). - dvnB = new ComplianceDVN(address(this), address(this), recvUlnB, 0); + // (the harness does not forward msg.value to assignJob). assignJob is gated to the + // send library, so pass B's real send lib even though this receive-side DVN never + // expects the call. + dvnB = new ComplianceDVN(address(this), address(this), endpointSetup.sendLibs[1], recvUlnB, 0); // Install ComplianceDVN as the REQUIRED receive-side DVN for the A->B // pathway (delivery gating happens on the receive side, eid B, srcEid A). @@ -201,8 +203,9 @@ contract ComplianceDvnVetoTest is TestHelperOz5 { bytes memory header = this._headerOf(packet); bytes32 payloadHash = this._payloadHashOf(packet); - // Operator (this test) routes verification THROUGH our ComplianceDVN. - dvnB.submitVerification(header, payloadHash, CONFIRMATIONS); + // Operator (this test) routes verification THROUGH our ComplianceDVN, carrying the + // allow verdict that permitted it (score 0, no reasons). + dvnB.submitVerification(header, payloadHash, CONFIRMATIONS, dvnB.ACTION_ALLOW(), 0, 0, bytes32(0)); // Now the required DVN has verified -> commit succeeds. IReceiveUlnConfigurable(address(dvnB.receiveUln())).commitVerification(header, payloadHash); diff --git a/test/hardhat/ComplianceDVN.test.ts b/test/hardhat/ComplianceDVN.test.ts new file mode 100644 index 0000000..47f3e55 --- /dev/null +++ b/test/hardhat/ComplianceDVN.test.ts @@ -0,0 +1,217 @@ +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { expect } from 'chai' +import { BigNumber, Contract, ContractTransaction } from 'ethers' +import { ethers } from 'hardhat' + +/** + * Contract-level tests for ComplianceDVN, runnable with `pnpm test:hardhat`. + * + * These mirror the foundry suite so the contract can be verified without a `forge` toolchain, + * which matters most right before a testnet deployment. + * + * This project does not install @nomicfoundation/hardhat-chai-matchers, so reverts and events + * are asserted with plain chai plus explicit log parsing rather than `.to.emit()` sugar. + */ +describe('ComplianceDVN', () => { + const ACTION = { ALLOW: 0, DELAY: 1, MANUAL_REVIEW: 2, BLOCK: 3 } + const PAYLOAD = ethers.utils.keccak256(ethers.utils.toUtf8Bytes('payload')) + const EVIDENCE = ethers.utils.keccak256(ethers.utils.toUtf8Bytes('evidence')) + const ZERO32 = ethers.constants.HashZero + + let owner: SignerWithAddress + let operator: SignerWithAddress + let stranger: SignerWithAddress + let sendLib: SignerWithAddress + let dvn: Contract + let receiveUln: Contract + + /** Assert the call reverts and that the revert names `expected`. */ + async function expectRevert(call: Promise, expected: string): Promise { + const sentinel = `__expected revert (${expected}) but the call succeeded__` + try { + await call + throw new Error(sentinel) + } catch (err) { + const message = (err as Error).message + if (message === sentinel) throw err + expect(message, `revert for ${expected}`).to.contain(expected) + } + } + + /** Parse the named event out of a transaction's own logs. */ + async function eventArgs(tx: Promise, contract: Contract, name: string) { + const receipt = await (await tx).wait() + const found = receipt.logs + .filter((log) => log.address.toLowerCase() === contract.address.toLowerCase()) + .map((log) => { + try { + return contract.interface.parseLog(log) + } catch { + return undefined + } + }) + .find((parsed) => parsed?.name === name) + expect(found, `event ${name} was not emitted`).to.not.be.undefined + return found!.args + } + + before(async () => { + ;[owner, operator, stranger, sendLib] = await ethers.getSigners() + }) + + beforeEach(async () => { + receiveUln = await (await ethers.getContractFactory('ReceiveUlnMock')).deploy() + const DVN = await ethers.getContractFactory('ComplianceDVN') + dvn = await DVN.deploy(owner.address, operator.address, sendLib.address, receiveUln.address, 0) + await dvn.deployed() + }) + + // Part of the event ABI: an indexer decoding old logs depends on these staying put. + it('pins the action codes', async () => { + expect(await dvn.ACTION_ALLOW()).to.equal(ACTION.ALLOW) + expect(await dvn.ACTION_DELAY()).to.equal(ACTION.DELAY) + expect(await dvn.ACTION_MANUAL_REVIEW()).to.equal(ACTION.MANUAL_REVIEW) + expect(await dvn.ACTION_BLOCK()).to.equal(ACTION.BLOCK) + }) + + describe('submitVerification', () => { + it('forwards the attestation to the ULN and emits the verdict in one call', async () => { + const args = await eventArgs( + dvn.connect(operator).submitVerification('0x0102', PAYLOAD, 7, ACTION.ALLOW, 12, 5, EVIDENCE), + dvn, + 'RiskVerdict' + ) + expect(args.payloadHash).to.equal(PAYLOAD) + expect(args.action).to.equal(ACTION.ALLOW) + expect(args.score).to.equal(12) + expect((args.reasonMask as BigNumber).toString()).to.equal('5') + expect(args.evidenceHash).to.equal(EVIDENCE) + + expect((await receiveUln.calls()).toString()).to.equal('1') + expect(await receiveUln.lastHeader()).to.equal('0x0102') + expect(await receiveUln.lastPayloadHash()).to.equal(PAYLOAD) + expect((await receiveUln.lastConfirmations()).toString()).to.equal('7') + }) + + it('rejects a caller that is not the operator', async () => { + await expectRevert( + dvn.connect(stranger).submitVerification('0x01', PAYLOAD, 5, ACTION.ALLOW, 0, 0, ZERO32), + 'NotOperator' + ) + }) + + // A packet that was blocked or held cannot also have been verified: the audit trail must + // not be able to contradict itself. + it('rejects any action other than allow, without reaching the ULN', async () => { + for (const action of [ACTION.DELAY, ACTION.MANUAL_REVIEW, ACTION.BLOCK]) { + await expectRevert( + dvn.connect(operator).submitVerification('0x01', PAYLOAD, 5, action, 0, 0, ZERO32), + 'VerificationRequiresAllow' + ) + } + expect((await receiveUln.calls()).toString()).to.equal('0') + }) + }) + + describe('recordVerdict', () => { + it('emits the verdict for the operator', async () => { + const args = await eventArgs( + dvn.connect(operator).recordVerdict(PAYLOAD, ACTION.BLOCK, 100, 1, EVIDENCE), + dvn, + 'RiskVerdict' + ) + expect(args.action).to.equal(ACTION.BLOCK) + expect(args.score).to.equal(100) + expect(args.evidenceHash).to.equal(EVIDENCE) + }) + + // The worker's reason mask uses bit 255 for unmapped codes, so the full width must survive. + it('carries a full uint256 reason mask intact', async () => { + const mask = BigNumber.from(1).shl(255).or(BigNumber.from(1).shl(4)) + const args = await eventArgs( + dvn.connect(operator).recordVerdict(PAYLOAD, ACTION.MANUAL_REVIEW, 70, mask, EVIDENCE), + dvn, + 'RiskVerdict' + ) + expect((args.reasonMask as BigNumber).toString()).to.equal(mask.toString()) + }) + + it('rejects a caller that is not the operator', async () => { + await expectRevert( + dvn.connect(stranger).recordVerdict(PAYLOAD, ACTION.BLOCK, 100, 1, ZERO32), + 'NotOperator' + ) + }) + + // An allow rides along on submitVerification; recording one here would double-report it. + it('rejects allow', async () => { + await expectRevert( + dvn.connect(operator).recordVerdict(PAYLOAD, ACTION.ALLOW, 0, 0, ZERO32), + 'AllowNotSeparatelyRecorded' + ) + }) + + it('rejects an out-of-range action', async () => { + await expectRevert(dvn.connect(operator).recordVerdict(PAYLOAD, 4, 0, 0, ZERO32), 'UnknownAction') + }) + }) + + describe('approvePacket', () => { + it('emits for the owner', async () => { + const args = await eventArgs(dvn.connect(owner).approvePacket(PAYLOAD), dvn, 'PacketApproved') + expect(args.payloadHash).to.equal(PAYLOAD) + expect(args.approver).to.equal(owner.address) + }) + + // The whole point of owner-gating: the worker holds only the operator key, so it cannot + // release the packets it chose to withhold. + it('rejects the operator', async () => { + await expectRevert(dvn.connect(operator).approvePacket(PAYLOAD), 'OwnableUnauthorizedAccount') + }) + + it('rejects a stranger', async () => { + await expectRevert(dvn.connect(stranger).approvePacket(PAYLOAD), 'OwnableUnauthorizedAccount') + }) + }) + + describe('assignJob', () => { + const param = { + dstEid: 40245, + packetHeader: '0x01', + payloadHash: PAYLOAD, + confirmations: 5, + sender: '0x0000000000000000000000000000000000001234', + } + + // SendUln302 calls assignJob with msg.value == 0 (it accrues worker fees internally), so + // requiring payment here would revert every real send. + it('succeeds with zero value and returns the fee quote', async () => { + const fee = ethers.utils.parseEther('0.00005') + const DVN = await ethers.getContractFactory('ComplianceDVN') + const paid = await DVN.deploy(owner.address, operator.address, sendLib.address, receiveUln.address, fee) + const quoted: BigNumber = await paid.connect(sendLib).callStatic.assignJob(param, '0x', { value: 0 }) + expect(quoted.toString()).to.equal(fee.toString()) + const args = await eventArgs( + paid.connect(sendLib).assignJob(param, '0x', { value: 0 }), + paid, + 'JobAssigned' + ) + expect(args.payloadHash).to.equal(PAYLOAD) + }) + + // The worker treats a JobAssigned payloadHash as "ours to screen" and spends operator gas + // verifying it, so anyone able to assign jobs could point the worker at packets no one + // asked it to verify. + it('rejects a caller that is not the send library', async () => { + await expectRevert(dvn.connect(stranger).assignJob(param, '0x', { value: 0 }), 'NotSendLibrary') + await expectRevert(dvn.connect(owner).assignJob(param, '0x', { value: 0 }), 'NotSendLibrary') + }) + + it('follows a setSendUln change', async () => { + await dvn.connect(owner).setSendUln(stranger.address) + const args = await eventArgs(dvn.connect(stranger).assignJob(param, '0x', { value: 0 }), dvn, 'JobAssigned') + expect(args.payloadHash).to.equal(PAYLOAD) + await expectRevert(dvn.connect(sendLib).assignJob(param, '0x', { value: 0 }), 'NotSendLibrary') + }) + }) +}) diff --git a/worker/.env.example b/worker/.env.example index 53ca658..b7ef3bc 100644 --- a/worker/.env.example +++ b/worker/.env.example @@ -2,8 +2,19 @@ # Copy to .env (gitignored) and fill in. The worker validates every value at boot # and refuses to start with an aggregated error if anything is missing/malformed. -# REQUIRED — signer key for submitVerification/commitVerification (0x + 64 hex). -PRIVATE_KEY= +# REQUIRED — the OPERATOR key the worker signs submitVerification / commitVerification / +# recordVerdict with (0x + 64 hex). Must be funded on every enabled chain: verify and commit run +# on the DESTINATION chain, so a bidirectional pathway needs balance on both. +# +# Deliberately NOT called PRIVATE_KEY: the repo root's .env has a PRIVATE_KEY and there it is the +# OWNER key. Sharing the name meant copying that file here silently gave the worker owner rights. +# The worker now refuses to start on a bare PRIVATE_KEY and says why. +OPERATOR_PRIVATE_KEY= + +# CLI ONLY — the owner key used by `dvn-cli approve` to release a packet held for manual review. +# `approvePacket` is owner-gated precisely so the worker CANNOT approve its own holds. Set it in +# your shell when approving; never in the service's environment. +# OWNER_PRIVATE_KEY= # REQUIRED per enabled chain — the deployed ComplianceDVN address (0x + 40 hex). DVN_BASE_SEPOLIA= @@ -26,15 +37,43 @@ DENYLIST_REFRESH_MS=1800000 # refresh the denylist every 30 min MAX_DENYLIST_STALENESS_MS=3600000 # HALT (withhold all verification) if the list ages past 1 h # must be >= DENYLIST_REFRESH_MS +# ── Signed indexer feed ───────────────────────────────────────────────────── +# The external indexer publishes graph-derived labels (N-hop exposure, mixer proximity) as a +# signed document. Leave INDEXER_FEED_URL empty to run on authoritative sources only. +INDEXER_FEED_URL= + +# REQUIRED whenever INDEXER_FEED_URL is set — comma-separated allowlist of signer addresses. +# A correctly signed feed from any other key is rejected. The worker refuses to boot with a +# feed URL and no allowlist: ingesting unverified labels is worse than having none. +INDEXER_SIGNERS= + +FEED_MAX_SKEW_SEC=300 # how far ahead of our clock generatedAt may sit + +# What to do when the feed is unavailable but the sanctions sources are fine: +# degrade — keep verifying on OFAC/OpenSanctions alone (feed labels absent). Default. +# halt — withhold all verification until the feed returns. +DEGRADED_MODE=degrade + # ── Transaction sending ───────────────────────────────────────────────────── TX_MAX_RETRIES=3 # bounded retries on transient send errors (0–20) TX_GAS_BUMP_PCT=15 # gas price increase per retry attempt (%) # ── Operational surface ───────────────────────────────────────────────────── +# Which non-allow actions get a separate recordVerdict transaction (comma-separated). +# `allow` is never listed — it rides along on submitVerification at no extra cost and is always +# recorded. Set to empty to emit nothing beyond allow. +EMIT_VERDICT_EVENTS=block + HTTP_PORT=9090 # serves /healthz /readyz /metrics LOG_LEVEL=info # fatal|error|warn|info|debug|trace|silent CHECKPOINT_PATH=.context/dvn-checkpoint.json NODE_ENV=production # 'development' enables pretty (non-JSON) logs # Operator-controlled flagged address for the veto demo (an address you hold the key for). +# Loaded as an `operator` source with the `operator_deny` label — scores 100, so it blocks. TEST_DENYLIST= + +# Curated scam token addresses (comma-separated). Loaded with the `scam_token` label, which +# scores 100 — every transfer of a listed token is BLOCKED. Confirmed scams only. Ships empty; +# the signed indexer feed will supply this list later. +SCAM_TOKENS= diff --git a/worker/RUNBOOK.md b/worker/RUNBOOK.md index a1b31ca..529f45b 100644 --- a/worker/RUNBOOK.md +++ b/worker/RUNBOOK.md @@ -13,7 +13,7 @@ on any uncertainty it withholds rather than risk approving a sanctioned transfer - `runtime/tx-sender.ts` — nonce tracking, gas escalation, bounded retries. - `runtime/http.ts` — `/healthz` `/readyz` `/metrics`. - `runtime/lifecycle.ts` — graceful shutdown. -- `service.ts` — composes the above into the poll loop. `cli.ts` — operator one-shots. +- `service.ts` — composes the above into the poll loop. ## States (see `dvn_ready`, `dvn_halted`) @@ -30,12 +30,13 @@ the halt is screened once the worker recovers. Expect a short backlog spike on r ## Run locally ```bash -cp worker/.env.example worker/.env # fill PRIVATE_KEY + DVN_* addresses -pnpm worker # or: cd worker && pnpm start -pnpm cli -- assess 0x
-pnpm cli -- verify baseSepolia 0x --dry-run +cp worker/.env.example worker/.env # fill OPERATOR_PRIVATE_KEY + DVN_* addresses +pnpm worker # from the repo root; or cd worker, then pnpm start ``` +Held packets, screening results, and owner actions (approve / reject) live in the demo dashboard — +`demo/dashboard/` — or the worker's own HTTP surface (`/status`, `/pending`). + Health: `curl localhost:9090/healthz` · `:9090/readyz` · `:9090/metrics`. ## Deploy (Kubernetes) @@ -44,12 +45,14 @@ Health: `curl localhost:9090/healthz` · `:9090/readyz` · `:9090/metrics`. docker build -t ghcr.io/your-org/compliance-dvn-worker: worker/ # Create the Secret out-of-band (never commit it): kubectl create secret generic compliance-dvn-worker-secrets \ - --from-literal=PRIVATE_KEY=0x... \ + --from-literal=OPERATOR_PRIVATE_KEY=0x... \ --from-literal=DVN_BASE_SEPOLIA=0x... --from-literal=DVN_OPTIMISM_SEPOLIA=0x... kubectl apply -k worker/deploy/k8s/ # set the image tag in kustomization.yaml first ``` -Import `worker/deploy/grafana-dashboard.json` into Grafana (pick your Prometheus datasource). +Import `worker/deploy/grafana-dashboard.json` into Grafana (pick your Prometheus datasource). For a +local stack, `docker compose --profile observability up -d` in `indexer/` provisions this dashboard +too, scraping the worker at `host.docker.internal:9090`. > **Single replica only.** The checkpoint file and local nonce tracking assume one writer. > The Deployment pins `replicas: 1` with `strategy: Recreate`. Do not scale up. @@ -87,7 +90,7 @@ sanctions sources and RPC endpoints. 1. Fund the new operator address on every enabled chain. 2. Ensure the new address is authorized to call `submitVerification` on each ComplianceDVN (and `commitVerification` on the ReceiveUln, which is permissionless). -3. Update the `PRIVATE_KEY` in the Secret; `kubectl rollout restart deploy/compliance-dvn-worker`. +3. Update `OPERATOR_PRIVATE_KEY` in the Secret; `kubectl rollout restart deploy/compliance-dvn-worker`. 4. Local nonce tracking re-syncs from the chain on the next send — no manual nonce reset needed. ## Recovery / data diff --git a/worker/assess/README.md b/worker/assess/README.md index 8c98d01..27c24ed 100644 --- a/worker/assess/README.md +++ b/worker/assess/README.md @@ -1,65 +1,104 @@ # Extending `assess()` -This is the risk-judgment core. The DVN worker calls it before attesting a packet; if it -says `blocked`, the worker withholds verification and the cross-chain message never settles. -Everything you add here flows straight into that on-chain veto. +This is the risk-judgment core. The DVN worker calls it before attesting a packet; the action +it returns decides whether the cross-chain message settles, waits, or never settles at all. +Everything you add here flows straight into that on-chain outcome. ## What it does today -`assess(address)` is a direct-hit lookup against a prebuilt denylist. Nothing more. - -- `score` is binary: `100` if the address is in the list, `0` if not. -- `blocked` is just `score === 100`. -- The list is built once at startup from four sources (`buildDenylist`): OFAC SDN crypto +`assess(subject)` reads every live entry for a subject out of the `RiskStore`, turns each label +into a piece of `Evidence`, and hands the set to the policy engine. + +- `RiskStore` keeps one entry **per source** per subject, each with its own confidence, TTL, + and optional evidence hash. Per-source entries are deliberate: a feed entry that expires in + an hour must not drag a permanent OFAC label out of the store with it. +- `evaluate()` in `policy.ts` sums label weights (capped at 100), picks a candidate action from + `ACTION_THRESHOLDS`, then **clamps** it to a ceiling. Two things cap each claim: how far its + source is trusted (`SOURCE_TRUST`), and whether it asserts a direct hit (`DIRECT_HIT_LABELS`). + So a pile of `public_event` labels can total 100 and still only reach `delay`, and stacked + *derived* labels reach `manual-review` rather than blocking — an inference is not grounds for + freezing funds. The cap is applied **per claim** and the most permissive claim wins, so an + untrusted "sanctions" next to a trusted 1-hop label cannot combine into a block. +- `combine([...])` folds the sender, receiver, and OFT recipient into one verdict: worst action + wins, scores are maxed rather than summed. +- The store is built at startup from four sources (`buildRiskStore`): OFAC SDN crypto addresses, OpenSanctions crypto wallets, a curated mixer set, and an operator test list. -- `combine([...])` runs `assess` on the sender, receiver, and OFT recipient, and blocks if - any one of them is a hit. -So right now this is sanctions/mixer screening by membership. There is no proximity, no -behavioral signal, no graded risk. The `score` field and the "1-hop" idea are shape only; -the logic is not implemented. +Contract signals come from `providers/contract.ts`, which reads what a node can see for itself: +whether the address holds code, whether an EIP-1967 implementation slot is set +(`upgradeable_proxy`), and who controls it — if the owner/admin carries labels of its own, that +becomes `contract_admin_risk`. + +Token signals come from `providers/token.ts`. When a party is an OFT it resolves the ERC-20 that +OFT actually moves, then screens **that** address: curated `scam_token` labels come from the +store, and a `fake_stablecoin_suspect` label is raised when a token claims a major stablecoin's +symbol from a non-canonical address. Evidence about the token names the token as its `subject`, +not the OApp that moves it. + +Graph signals (`sanctions_1hop`, `sanctions_1hop_inbound`, `mixer_exposure`) and +`unverified_contract` come from the indexer feed, not from here — the first three need a +transfer graph and the last needs an external verifier, neither of which belongs in the packet +decision path. See `indexer/README.md`. + +Not implemented anywhere yet: `honeypot_suspect`, which needs transaction simulation. Its weight +exists in `policy.ts` but nothing populates it. ## The contract (do not break this) ```ts -interface Assessment { address: string; tags: string[]; score: number; reasons: string[]; blocked: boolean } -type Assessor = (address: string) => Assessment +type RiskAction = 'allow' | 'delay' | 'manual-review' | 'block' +interface Assessment { subject: string; score: number; action: RiskAction; reasonCodes: string[]; evidence: Evidence[] } +type Assessor = (subject: string, chainKey?: string) => Promise ``` -- `blocked` is the only field the worker acts on. `tags` / `reasons` / `score` are for logs - and the tracer. -- Addresses are lowercased everywhere. Keep it that way. -- `assess` is chain-independent. Do not pass chain context into it. -- The worker calls `assess` inline before each attestation, so keep it deterministic and - fast, and fail closed: if a signal is unavailable, do not silently return `blocked: false`. +- `action` is what the worker acts on. `score` / `reasonCodes` / `evidence` explain it. +- Subjects are lowercased everywhere. Keep it that way. +- `chainKey` selects which chain's state contract checks run against. A packet's parties do + **not** share a chain — the sender is an OApp on the source, the receiver and OFT recipient + are on the destination — so it cannot be inferred. Omit it for store-only screening. +- The worker calls `assess` inline before each attestation, so keep it bounded and cached, and + fail closed: when a signal is unavailable the verdict is floored at `delay`, never `allow`. +- A failed check must not become a scored label. Scoring it would let an RPC hiccup stack onto + an existing 70-point label and cross the block threshold; the floor keeps the two separate. ## Where to add logic | You want to | Touch | Notes | | --- | --- | --- | -| Add a data source | new `ingest/.ts`, then wire it into `buildDenylist()` | Follow the `Fetcher` pattern in `ingest/ofac.ts` so it stays unit-testable offline. | -| Grade the risk score | `makeAssessor()` in `assess.ts` | Replace the binary `100`/`0` with a real score, and decide the `blocked` threshold there. | -| Change the veto policy | `combine()` in `assess.ts` | Today: block if any party hits. You might block on a combined score, or only on the recipient. | -| Add graph / behavioral signals (1-hop exposure, counterparty risk) | `assess.ts` + every call site | This needs data beyond a local set, so `Assessor` likely becomes `async`. See below. | +| Add a data source | new `ingest/.ts`, then wire it into `buildRiskStore()` | Follow the `Fetcher` pattern in `ingest/ofac.ts` so it stays unit-testable offline. Pick the right `LabelSource` — it decides how far the source can escalate. | +| Curate scam tokens | `SCAM_TOKENS` env, read by `ingest/tokens.ts` | `scam_token` scores 100, so an entry blocks every transfer of that token. Confirmed only. | +| Add a canonical stablecoin | `CANONICAL_STABLECOINS` in `providers/token.ts` | A wrong entry flags the REAL token. A symbol with no entry for the chain is not judged, so omitting a chain is safe and guessing is not. | +| Add a new signal | a label + weight in `LABEL_WEIGHTS`, then whatever populates it | Weights are policy, so bump `POLICY_VERSION` when you change them. Also add a bit to `REASON_BITS` in `verdict.ts` — append only, never renumber. | +| Let a new label cause a refusal | `DIRECT_HIT_LABELS` in `policy.ts` | Only for labels asserting the subject *is* the thing, not that it is near one. Everything else tops out at `manual-review` no matter how high it scores. | +| Change thresholds or the veto policy | `ACTION_THRESHOLDS` / `evaluate()` in `policy.ts` | These values are agreed with the team; do not tune them ad hoc. | +| Change how much a source is trusted | `SOURCE_TRUST` in `sources.ts` | The single place enforcement authority lives. | +| Add graph / behavioral signals (1-hop exposure, counterparty risk) | the indexer feed, then `ingest/feed.ts` | Depth and dusting thresholds live in `N_HOP`. The DVN does not walk the graph itself. | +| Add a chain-state signal | `providers/contract.ts` (or a sibling provider) | Read through the narrow `ChainReader` interface so tests stay offline, and keep every call inside the timeout. | + +### Adding a provider that does I/O -### Going async (graph lookups, external risk APIs) +`Assessor` is already async, so a new provider does not change the signature — but it does +share the packet loop's latency budget. Follow what `RpcContractInspector` does: -A local set lookup is sync. The moment you need a network or graph query, change the type to -`(address: string) => Promise` and update all three call sites to `await`: +- take a narrow reader interface, not an ethers provider (the adapter lives in `chain/reader.ts`) +- cache by `chainKey:address` with a TTL, and bound every lookup with a timeout +- do **not** cache failures, and do not let one optional sub-read (a reverting `owner()`) fail + the whole inspection +- throw on "cannot determine" rather than returning a clean-looking default; the caller turns + that into a hold -- `worker/service.ts` (`handlePacket` — the live veto path) -- `worker/cli.ts` (`cmdAssess`, `cmdVerify`) -- `worker/tracker/trace.ts` (`buildTrace`) +Call sites to keep in mind if the signature ever changes again: -Cache aggressively and bound latency. The worker blocks on this call per packet, and a slow -or failing lookup must fail closed (withhold), not pass. +- `worker/runtime/scanner.ts` (`verifyPacket`, `processDeferred` — the live decision paths) ## Run it ```bash pnpm test:worker # unit tests live in worker/test/ -pnpm cli assess
# one-shot: see the Assessment for any address ``` +Screening results and held packets are visible in the demo dashboard (`demo/dashboard/`) and on +the worker's HTTP surface (`/status`, `/pending`). + Add tests next to the existing ones (`worker/test/assess.spec.ts`, `ingest/*` specs). Keep network calls behind an injected fetcher so tests stay offline. diff --git a/worker/assess/assess.ts b/worker/assess/assess.ts index 996cfc9..cd3fa0c 100644 --- a/worker/assess/assess.ts +++ b/worker/assess/assess.ts @@ -1,46 +1,301 @@ -import { Denylist } from './store' +import { RiskStore } from './store' +import { evaluate, worseAction, LABEL_WEIGHTS, type PolicyEntry, type RiskAction } from './policy' +import type { LabelSource } from './sources' +import type { ContractInspector, ContractFacts } from './providers/contract' +import { isFakeStablecoin, type TokenInspector } from './providers/token' import { ingestOfac } from './ingest/ofac' import { ingestOpenSanctions } from './ingest/opensanctions' import { ingestMixers } from './ingest/mixers' +import { loadScamTokens } from './ingest/tokens' +import { ingestFeed, type FeedConfig, type IngestFeedDeps } from './ingest/feed' import { loadTestDenylist } from './testDenylist' +export type { RiskAction } from './policy' + +/** One scored signal, carrying enough context to explain the verdict after the fact. */ +export interface Evidence { + /** The label, which doubles as the reason code. */ + type: string + weight: number + confidence: number + source: LabelSource + /** + * What the signal is about. Usually the assessed subject, but not always: token evidence + * discovered through an OFT names the token, not the OApp that moves it. + */ + subject: string + details?: Record +} + export interface Assessment { - address: string - tags: string[] - score: number // 0 clean, 100 direct hit - reasons: string[] - blocked: boolean + subject: string + score: number + action: RiskAction + reasonCodes: string[] + evidence: Evidence[] +} + +/** + * Screen one subject. `chainKey` selects which chain's state the live checks run against — + * a packet's parties do not all live on the same chain, so it cannot be inferred. Omit it for + * store-only screening (the CLI's one-shot lookups and the tracer). + */ +export type Assessor = (subject: string, chainKey?: string) => Promise + +/** Reason codes for a live check that could not be completed. Never scored — see below. */ +export const CONTRACT_CHECK_UNAVAILABLE = 'contract_check_unavailable' +export const TOKEN_CHECK_UNAVAILABLE = 'token_check_unavailable' + +export interface AssessorProviders { + /** Live contract-state checks (code, proxy, controller). */ + contracts?: ContractInspector + /** Resolves the ERC-20 an OFT moves and reads its metadata. */ + tokens?: TokenInspector } -export type Assessor = (address: string) => Assessment +/** A label plus the subject it is about and any supporting detail. */ +interface Finding { + label: string + subject: string + source: LabelSource + confidence: number + assertedScore?: number + details?: Record +} -/** Direct-hit assessor over a prebuilt denylist. Chain-independent. */ -export function makeAssessor(dl: Denylist): Assessor { - return (address: string): Assessment => { - const e = dl.lookup(address) - if (!e) return { address: address.toLowerCase(), tags: [], score: 0, reasons: [], blocked: false } - return { address: e.address, tags: e.tags, score: 100, reasons: e.reasons, blocked: true } +/** + * Group findings the way the policy scores them: one entry per (subject, source) claim. + * + * An asserted score describes a subject, so it must be counted once per claim. Keying by subject + * as well as source keeps a token's labels from merging into the OApp's — they are separate + * claims about separate things that happen to share a source. + */ +function toPolicyEntries(findings: Finding[]): PolicyEntry[] { + const byClaim = new Map() + for (const f of findings) { + const key = `${f.subject}|${f.source}` + const existing = byClaim.get(key) + if (existing) { + existing.labels.push(f.label) + if (f.assertedScore !== undefined) { + existing.assertedScore = Math.max(existing.assertedScore ?? 0, f.assertedScore) + } + } else { + byClaim.set(key, { source: f.source, labels: [f.label], assertedScore: f.assertedScore }) + } } + return [...byClaim.values()] +} + +/** Every label the store holds about a subject, as findings. */ +function storeFindings(subject: string, store: RiskStore): Finding[] { + return store.lookup(subject).flatMap((entry) => + entry.labels.map((label) => ({ + label, + subject, + source: entry.source, + confidence: entry.confidence, + assertedScore: entry.score, + details: { subjectType: entry.subjectType, ...(entry.evidenceHash ? { evidenceHash: entry.evidenceHash } : {}) }, + })), + ) } -/** Block if ANY party is flagged. Aggregates tags/reasons. */ +/** Turn on-chain contract facts into findings. Controller risk is looked up in the store. */ +function contractFindings(subject: string, facts: ContractFacts, store: RiskStore): Finding[] { + if (!facts.isContract) return [] + const out: Finding[] = [] + const own = { subject, source: 'operator' as LabelSource, confidence: 1 } + + if (facts.proxy) { + out.push({ ...own, label: 'upgradeable_proxy', details: { implementation: facts.implementation } }) + } + if (facts.controller) { + const controllerLabels = store.lookup(facts.controller).flatMap((e) => e.labels) + if (controllerLabels.length) { + out.push({ ...own, label: 'contract_admin_risk', details: { controller: facts.controller, controllerLabels } }) + } + } + return out +} + +/** + * Assessor over a risk store, optionally enriched with live chain checks. + * + * With no providers this is a pure store lookup. With them, a subject that holds code also + * contributes proxy and controller-risk evidence, and an OFT contributes evidence about the + * token it moves — curated labels on that token, plus a stablecoin-impersonation check. + * + * A failed live check does NOT become a scored label. Scoring it would let an RPC hiccup stack + * on top of an existing 70-point label and cross the block threshold. Instead the verdict is + * floored at `delay`: the packet is held and re-screened, which is the honest outcome when a + * signal is missing rather than clean. + */ +export function makeAssessor(store: RiskStore, providers: AssessorProviders = {}): Assessor { + return async (subject: string, chainKey?: string): Promise => { + const normalized = subject.toLowerCase() + const findings: Finding[] = storeFindings(normalized, store) + const unavailable: string[] = [] + + if (chainKey) { + let facts: ContractFacts | undefined + if (providers.contracts) { + try { + facts = await providers.contracts.inspect(normalized, chainKey) + findings.push(...contractFindings(normalized, facts, store)) + } catch (err) { + unavailable.push(CONTRACT_CHECK_UNAVAILABLE) + findings.push(unavailableFinding(CONTRACT_CHECK_UNAVAILABLE, normalized, chainKey, err)) + } + } + + // Only OApps that hold code can be an OFT. When contract facts are missing we still try, + // since a wrong skip here would silently drop token screening. + if (providers.tokens && facts?.isContract !== false) { + const resolution = await providers.tokens.resolveToken(normalized, chainKey) + if (resolution.kind === 'unknown') { + unavailable.push(TOKEN_CHECK_UNAVAILABLE) + findings.push(unavailableFinding(TOKEN_CHECK_UNAVAILABLE, normalized, chainKey, new Error(resolution.reason))) + } else if (resolution.kind === 'token') { + // Curated labels on the token need no RPC, so they are collected BEFORE the metadata + // read — otherwise an unrelated RPC failure would discard a definite `scam_token` + // block and downgrade the packet to a mere hold. + findings.push(...storeFindings(resolution.address, store)) + try { + const facts = await providers.tokens.inspect(resolution.address, chainKey) + if (isFakeStablecoin(facts, chainKey)) { + findings.push({ + label: 'fake_stablecoin_suspect', + subject: resolution.address, + source: 'operator', + confidence: 1, + details: { symbol: facts.symbol, decimals: facts.decimals, chainKey }, + }) + } + } catch (err) { + unavailable.push(TOKEN_CHECK_UNAVAILABLE) + findings.push(unavailableFinding(TOKEN_CHECK_UNAVAILABLE, resolution.address, chainKey, err)) + } + } + } + } + + const decision = evaluate(toPolicyEntries(findings.filter((f) => !unavailable.includes(f.label)))) + + const evidence: Evidence[] = findings.map((f) => ({ + type: f.label, + weight: unavailable.includes(f.label) ? 0 : LABEL_WEIGHTS[f.label] ?? 0, + confidence: f.confidence, + source: f.source, + subject: f.subject, + details: f.details, + })) + + if (unavailable.length === 0) return { subject: normalized, ...decision, evidence } + + // Fail closed: hold rather than pass on a subject we could not fully screen. + return { + subject: normalized, + score: decision.score, + action: worseAction(decision.action, 'delay'), + reasonCodes: [...decision.reasonCodes, ...unavailable], + evidence, + } + } +} + +function unavailableFinding(label: string, subject: string, chainKey: string, err: unknown): Finding { + return { + label, + subject, + source: 'operator', + confidence: 0, + details: { chainKey, error: (err as Error).message }, + } +} + +/** + * Fold per-party assessments into the packet's verdict: the worst action wins. + * + * Scores are maxed rather than summed — a sender and a receiver each scoring 70 describes two + * separate risks, not one at 140, and summing them would silently promote two `manual-review` + * parties into a `block`. + */ export function combine(parts: Assessment[]): Assessment { - const blocked = parts.some((p) => p.blocked) return { - address: parts.map((p) => p.address).join(','), - tags: [...new Set(parts.flatMap((p) => p.tags))], + subject: parts.map((p) => p.subject).join(','), score: Math.max(0, ...parts.map((p) => p.score)), - reasons: parts.flatMap((p) => p.reasons), - blocked, + action: parts.reduce((acc, p) => worseAction(acc, p.action), 'allow'), + reasonCodes: [...new Set(parts.flatMap((p) => p.reasonCodes))], + evidence: parts.flatMap((p) => p.evidence), } } -/** Build a denylist from all real sources + operator test entries. */ -export async function buildDenylist(): Promise { - const dl = new Denylist() - await ingestOfac(dl) - await ingestOpenSanctions(dl) - ingestMixers(dl) - loadTestDenylist(dl) - return dl +/** The outcome of one build, not just its result. */ +export interface RiskStoreBuild { + store: RiskStore + /** + * Sources that failed but were tolerated, so the caller can decide whether to keep verifying. + * Empty on a clean build. Authoritative sanctions sources are never listed here — their + * failure aborts the build outright. + */ + degraded: string[] +} + +export interface BuildRiskStoreOptions { + /** Signed indexer feed. Omit to run on authoritative sources only. */ + feed?: FeedConfig + feedDeps?: IngestFeedDeps + /** Called when a tolerated source fails, so the caller can log and count it. */ + onDegraded?: (source: string, err: Error) => void +} + +/** + * Build a risk store from all sources. + * + * The sanctions sources are load-bearing: if OFAC or OpenSanctions cannot be fetched we throw, + * and the caller's fail-closed lifecycle withholds verification. The indexer feed is not treated + * the same way — losing graph labels should not also cost us sanctions screening — so a feed + * failure is reported as degraded and the build still succeeds. Whether degraded is allowed to + * keep verifying is the operator's call, not this function's. + */ +/** + * Re-ingest just the indexer feed into an existing store. + * + * A full rebuild re-downloads OFAC and OpenSanctions, which is why it runs on a long timer. The + * feed is a single local request, so it can be refreshed far more often — which is what makes a + * newly published graph label visible in seconds instead of on the next rebuild. + * + * Feed entries expire on their own (`expiresAt`), so ingesting into a live store rather than a + * fresh one is safe: an address the indexer drops stops being scored when its TTL lapses, and the + * next full rebuild removes it outright. + */ +export async function refreshFeedInto(store: RiskStore, opts: BuildRiskStoreOptions = {}): Promise { + if (!opts.feed || !opts.feedDeps) return 0 + try { + return await ingestFeed(store, opts.feed, opts.feedDeps) + } catch (err) { + opts.onDegraded?.('trusted_indexer', err as Error) + return 0 + } +} + +export async function buildRiskStore(opts: BuildRiskStoreOptions = {}): Promise { + const store = new RiskStore() + await ingestOfac(store) + await ingestOpenSanctions(store) + ingestMixers(store) + loadScamTokens(store) + loadTestDenylist(store) + + const degraded: string[] = [] + if (opts.feed && opts.feedDeps) { + try { + await ingestFeed(store, opts.feed, opts.feedDeps) + } catch (err) { + degraded.push('trusted_indexer') + opts.onDegraded?.('trusted_indexer', err as Error) + } + } + return { store, degraded } } diff --git a/worker/assess/canonical.ts b/worker/assess/canonical.ts new file mode 100644 index 0000000..dd81cbc --- /dev/null +++ b/worker/assess/canonical.ts @@ -0,0 +1,24 @@ +/** + * Deterministic JSON: object keys sorted, arrays left in order, no whitespace. + * + * Used wherever two independent implementations must agree on bytes — signing an indexer feed, + * and hashing an evidence document that the indexer will later re-derive. Non-integer numbers + * are rejected because float formatting is not guaranteed to round-trip identically across + * languages, so one carrying a float could hash differently on each side. + */ +export function canonicalize(value: unknown): string { + if (value === undefined) throw new Error('cannot canonicalize undefined') + if (value === null || typeof value !== 'object') { + if (typeof value === 'number' && !Number.isInteger(value)) { + throw new Error(`non-integer number cannot be canonicalized: ${value}`) + } + return JSON.stringify(value) + } + if (Array.isArray(value)) return `[${value.map(canonicalize).join(',')}]` + const obj = value as Record + const parts = Object.keys(obj) + .sort() + .filter((k) => obj[k] !== undefined) + .map((k) => `${JSON.stringify(k)}:${canonicalize(obj[k])}`) + return `{${parts.join(',')}}` +} diff --git a/worker/assess/ingest/feed.ts b/worker/assess/ingest/feed.ts new file mode 100644 index 0000000..7a02bc2 --- /dev/null +++ b/worker/assess/ingest/feed.ts @@ -0,0 +1,261 @@ +import { ethers } from 'ethers' +import { RiskStore, type SubjectType } from '../store' +import { POLICY_VERSION } from '../policy' +import { canonicalize } from '../canonical' +import type { Fetcher } from './ofac' + +export { canonicalize } + +/** + * Signed indexer feed ingest. + * + * The external indexer computes what the DVN deliberately does not — graph exposure, N-hop + * proximity, label propagation — and publishes it as a signed document. This module is the only + * way those labels enter the worker, and every one of the checks below is load-bearing: a feed + * that fails any of them is rejected whole rather than partially applied. + * + * Entries land as the `trusted_indexer` source, so `SOURCE_TRUST` still bounds how far they can + * escalate. A valid signature proves who produced the feed, not that its contents are true. + */ + +const EVM_ADDRESS = /^0x[0-9a-fA-F]{40}$/ +const HEX32 = /^0x[0-9a-fA-F]{64}$/ +const SIGNATURE = /^0x[0-9a-fA-F]{130}$/ +const SUBJECT_TYPES: SubjectType[] = ['address', 'contract', 'token'] + +export interface FeedEntry { + address: string + labels: string[] + score?: number + subjectType?: SubjectType + evidenceHash?: string +} + +export interface Feed { + version: number + /** Unix seconds. */ + generatedAt: number + /** Unix seconds. Doubles as the TTL stamped onto every entry. */ + expiresAt: number + source: string + policyVersion: number + entries: FeedEntry[] + signature: string +} + +export type FeedRejection = + | 'fetch_failed' + | 'malformed' + | 'bad_signature' + | 'untrusted_signer' + | 'policy_mismatch' + | 'replayed' + | 'expired' + | 'future_dated' + +export class FeedError extends Error { + constructor(readonly reason: FeedRejection, message: string) { + super(`feed rejected (${reason}): ${message}`) + this.name = 'FeedError' + } +} + +export interface FeedConfig { + url: string + /** Allowlisted signer addresses. A correct signature from anyone else is still rejected. */ + signers: string[] + /** How far ahead of our clock `generatedAt` may sit before we call it future-dated. */ + maxSkewSec?: number +} + +/** Monotonic version-per-source, persisted so a restart cannot be replayed an old feed. */ +export interface FeedVersionStore { + get(source: string): number + set(source: string, version: number): void +} + +export interface IngestFeedDeps { + fetcher?: Fetcher + now?: () => number + versions: FeedVersionStore +} + +/** + * The exact bytes a signer covers: the whole document except `signature`. + * + * Every numeric field in the feed format is an integer by design, which is what makes + * `canonicalize` usable here — confidence is deliberately not one of them; it comes from + * `SOURCE_TRUST`, since how far to trust a source is our judgement, not the source's own claim. + */ +export function signingPayload(raw: Record): string { + const { signature: _signature, ...rest } = raw + return canonicalize(rest) +} + +function requireInt(v: unknown, field: string): number { + if (typeof v !== 'number' || !Number.isInteger(v) || v < 0) { + throw new FeedError('malformed', `${field} must be a non-negative integer`) + } + return v +} + +/** + * Validate the document's shape by hand rather than stripping it through a schema. + * + * The signature covers the delivered bytes, so unknown fields must be preserved for + * verification — a parser that silently dropped them would break every feed whose producer + * included a field we do not know about. + */ +export function parseFeed(body: string): { feed: Feed; raw: Record } { + let raw: unknown + try { + raw = JSON.parse(body) + } catch (err) { + throw new FeedError('malformed', (err as Error).message) + } + if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) { + throw new FeedError('malformed', 'feed must be a JSON object') + } + const o = raw as Record + + if (typeof o.source !== 'string' || o.source.length === 0) { + throw new FeedError('malformed', 'source must be a non-empty string') + } + if (typeof o.signature !== 'string' || !SIGNATURE.test(o.signature)) { + throw new FeedError('malformed', 'signature must be a 65-byte hex string') + } + if (!Array.isArray(o.entries)) throw new FeedError('malformed', 'entries must be an array') + + const entries: FeedEntry[] = o.entries.map((e, i) => { + if (e === null || typeof e !== 'object') throw new FeedError('malformed', `entries[${i}] must be an object`) + const entry = e as Record + if (typeof entry.address !== 'string' || !EVM_ADDRESS.test(entry.address)) { + throw new FeedError('malformed', `entries[${i}].address must be a 20-byte EVM address`) + } + if (!Array.isArray(entry.labels) || entry.labels.length === 0) { + throw new FeedError('malformed', `entries[${i}].labels must be a non-empty array`) + } + const labels = entry.labels.map((l) => { + if (typeof l !== 'string' || l.length === 0) { + throw new FeedError('malformed', `entries[${i}].labels must contain non-empty strings`) + } + return l + }) + if (entry.score !== undefined) { + const score = requireInt(entry.score, `entries[${i}].score`) + if (score > 100) throw new FeedError('malformed', `entries[${i}].score must be <= 100`) + } + if (entry.subjectType !== undefined && !SUBJECT_TYPES.includes(entry.subjectType as SubjectType)) { + throw new FeedError('malformed', `entries[${i}].subjectType is not a known subject type`) + } + if (entry.evidenceHash !== undefined && (typeof entry.evidenceHash !== 'string' || !HEX32.test(entry.evidenceHash))) { + throw new FeedError('malformed', `entries[${i}].evidenceHash must be a 32-byte hex string`) + } + // NOTE: a feed may carry a per-entry `action`. It is intentionally ignored — the action is + // ours to decide from policy, and honouring the feed's would hand enforcement authority to + // the indexer, which is exactly what SOURCE_TRUST exists to prevent. + return { + address: entry.address.toLowerCase(), + labels, + score: entry.score as number | undefined, + subjectType: entry.subjectType as SubjectType | undefined, + evidenceHash: entry.evidenceHash as string | undefined, + } + }) + + return { + feed: { + version: requireInt(o.version, 'version'), + generatedAt: requireInt(o.generatedAt, 'generatedAt'), + expiresAt: requireInt(o.expiresAt, 'expiresAt'), + source: o.source, + policyVersion: requireInt(o.policyVersion, 'policyVersion'), + entries, + signature: o.signature, + }, + raw: o, + } +} + +/** Recover the signer and check it against the allowlist. */ +export function verifyFeedSigner(raw: Record, signature: string, signers: string[]): string { + let recovered: string + try { + recovered = ethers.utils.verifyMessage(signingPayload(raw), signature).toLowerCase() + } catch (err) { + throw new FeedError('bad_signature', (err as Error).message) + } + const allowed = signers.map((s) => s.toLowerCase()) + if (!allowed.includes(recovered)) { + throw new FeedError('untrusted_signer', `${recovered} is not in the signer allowlist`) + } + return recovered +} + +const defaultFetch: Fetcher = async (url) => { + const fetch = (await import('node-fetch')).default + const res = await fetch(url) + if (!res.ok) throw new Error(`indexer feed fetch failed: ${res.status}`) + return res.text() +} + +/** + * Fetch, verify, and apply one feed. Returns the number of entries applied. + * + * Nothing is written to the store until every check passes, so a rejected feed leaves the + * previous state untouched rather than half-applied. Entries expire with the feed itself: each + * carries `expiresAt`, so once the document goes stale its labels stop being scored without any + * purge step. Removals need no handling either — every refresh builds a fresh store, so an + * address the indexer drops is simply absent next time. + */ +export async function ingestFeed(store: RiskStore, cfg: FeedConfig, deps: IngestFeedDeps): Promise { + const fetcher = deps.fetcher ?? defaultFetch + const now = deps.now ?? Date.now + const nowSec = Math.floor(now() / 1000) + const maxSkewSec = cfg.maxSkewSec ?? 300 + + let body: string + try { + body = await fetcher(cfg.url) + } catch (err) { + throw new FeedError('fetch_failed', (err as Error).message) + } + + const { feed, raw } = parseFeed(body) + verifyFeedSigner(raw, feed.signature, cfg.signers) + + if (feed.policyVersion !== POLICY_VERSION) { + throw new FeedError( + 'policy_mismatch', + `feed policyVersion ${feed.policyVersion} != worker POLICY_VERSION ${POLICY_VERSION}`, + ) + } + // Only a rollback is a replay. Re-applying the version we already hold is how a fresh store (a + // full rebuild, or a restart) recovers the current labels, and how a polling refresh reads a feed + // that simply has not changed yet — rejecting it there would drop every feed-derived label until + // the indexer next published, and report a healthy source as unavailable meanwhile. + const lastVersion = deps.versions.get(feed.source) + if (feed.version < lastVersion) { + throw new FeedError('replayed', `version ${feed.version} is older than accepted ${lastVersion}`) + } + if (feed.expiresAt <= nowSec) { + throw new FeedError('expired', `expiresAt ${feed.expiresAt} is not in the future (now ${nowSec})`) + } + if (feed.generatedAt > nowSec + maxSkewSec) { + throw new FeedError('future_dated', `generatedAt ${feed.generatedAt} is more than ${maxSkewSec}s ahead`) + } + + for (const entry of feed.entries) { + store.upsert({ + subject: entry.address, + subjectType: entry.subjectType ?? 'address', + labels: entry.labels, + source: 'trusted_indexer', + score: entry.score, + evidenceHash: entry.evidenceHash, + expiresAt: feed.expiresAt * 1000, + }) + } + deps.versions.set(feed.source, feed.version) + return feed.entries.length +} diff --git a/worker/assess/ingest/mixers.ts b/worker/assess/ingest/mixers.ts index 08629eb..0d4098c 100644 --- a/worker/assess/ingest/mixers.ts +++ b/worker/assess/ingest/mixers.ts @@ -1,4 +1,4 @@ -import { Denylist } from '../store' +import { RiskStore } from '../store' /** Curated OFAC-sanctioned Tornado Cash contracts (mainnet). */ export const MIXER_ADDRESSES: string[] = [ @@ -7,7 +7,15 @@ export const MIXER_ADDRESSES: string[] = [ '0x910cbd523d972eb0a6f4cae4618ad62622b39dbf', // Tornado.Cash 100 ETH ].map((a) => a.toLowerCase()) -export function ingestMixers(dl: Denylist): number { - for (const a of MIXER_ADDRESSES) dl.add(a, 'mixer', 'Curated sanctioned mixer contract (Tornado Cash)') +/** Sanctioned by OFAC, so the entries carry `ofac` authority rather than operator authority. */ +export function ingestMixers(store: RiskStore): number { + for (const a of MIXER_ADDRESSES) { + store.upsert({ + subject: a, + subjectType: 'contract', + labels: ['sanctioned_mixer'], + source: 'ofac', + }) + } return MIXER_ADDRESSES.length } diff --git a/worker/assess/ingest/ofac.ts b/worker/assess/ingest/ofac.ts index e8c5507..97fd2b2 100644 --- a/worker/assess/ingest/ofac.ts +++ b/worker/assess/ingest/ofac.ts @@ -1,4 +1,4 @@ -import { Denylist } from '../store' +import { RiskStore } from '../store' const OFAC_ETH_URL = 'https://raw.githubusercontent.com/0xB10C/ofac-sanctioned-digital-currency-addresses/lists/sanctioned_addresses_ETH.json' @@ -23,9 +23,11 @@ const defaultFetch: Fetcher = async (url) => { return res.text() } -export async function ingestOfac(dl: Denylist, fetcher: Fetcher = defaultFetch): Promise { +export async function ingestOfac(store: RiskStore, fetcher: Fetcher = defaultFetch): Promise { const body = await fetcher(OFAC_ETH_URL) const addrs = parseOfacList(body) - for (const a of addrs) dl.add(a, 'ofac', 'OFAC SDN digital currency address (ETH)') + for (const a of addrs) { + store.upsert({ subject: a, subjectType: 'address', labels: ['sanctions'], source: 'ofac' }) + } return addrs.length } diff --git a/worker/assess/ingest/opensanctions.ts b/worker/assess/ingest/opensanctions.ts index 63e0bf2..155723e 100644 --- a/worker/assess/ingest/opensanctions.ts +++ b/worker/assess/ingest/opensanctions.ts @@ -1,4 +1,4 @@ -import { Denylist } from '../store' +import { RiskStore } from '../store' import type { Fetcher } from './ofac' const OS_OFAC_SDN_URL = 'https://data.opensanctions.org/datasets/latest/us_ofac_sdn/entities.ftm.json' @@ -29,9 +29,11 @@ const defaultFetch: Fetcher = async (url) => { return res.text() } -export async function ingestOpenSanctions(dl: Denylist, fetcher: Fetcher = defaultFetch): Promise { +export async function ingestOpenSanctions(store: RiskStore, fetcher: Fetcher = defaultFetch): Promise { const body = await fetcher(OS_OFAC_SDN_URL) const addrs = parseOpenSanctionsNdjson(body) - for (const a of addrs) dl.add(a, 'opensanctions', 'OpenSanctions us_ofac_sdn CryptoWallet') + for (const a of addrs) { + store.upsert({ subject: a, subjectType: 'address', labels: ['sanctions'], source: 'opensanctions' }) + } return addrs.length } diff --git a/worker/assess/ingest/tokens.ts b/worker/assess/ingest/tokens.ts new file mode 100644 index 0000000..6cbbca1 --- /dev/null +++ b/worker/assess/ingest/tokens.ts @@ -0,0 +1,25 @@ +import { RiskStore } from '../store' + +const isEvmAddress = (s: string) => /^0x[0-9a-fA-F]{40}$/.test(s) + +/** + * Operator-curated scam token list. + * + * Ships empty on purpose: a scam-token list is only as good as its provenance, and inventing + * entries here would put unverified addresses on a blocking path. Populate it from the + * `SCAM_TOKENS` env var until the signed indexer feed supplies it. + * + * `scam_token` scores 100, so an entry here BLOCKS every transfer of that token. Confirmed + * scams only — suspicion belongs in a lower-weight label. + */ +export function loadScamTokens(store: RiskStore, csv = process.env.SCAM_TOKENS || ''): number { + let n = 0 + for (const raw of csv.split(',')) { + const a = raw.trim().toLowerCase() + if (isEvmAddress(a)) { + store.upsert({ subject: a, subjectType: 'token', labels: ['scam_token'], source: 'operator' }) + n++ + } + } + return n +} diff --git a/worker/assess/policy.ts b/worker/assess/policy.ts new file mode 100644 index 0000000..e2a465c --- /dev/null +++ b/worker/assess/policy.ts @@ -0,0 +1,197 @@ +import { SOURCE_TRUST, type EnforcementLevel, type LabelSource } from './sources' + +/** + * Risk policy — the weights, thresholds, and timings that turn evidence into an action. + * + * These are code constants rather than env config on purpose: a policy change is a reviewed + * change, and `POLICY_VERSION` is bumped with it so an indexer feed generated under a + * different policy is rejected instead of silently mixed in. + */ + +/** v2: graph proximity extended from 1 hop to 3, with per-depth labels and weights. */ +export const POLICY_VERSION = 2 + +export type RiskAction = 'allow' | 'delay' | 'manual-review' | 'block' + +/** Severity order. Used to pick the worse of two actions and to apply source ceilings. */ +const SEVERITY: Record = { allow: 0, delay: 1, 'manual-review': 2, block: 3 } + +/** The more severe of two actions. */ +export function worseAction(a: RiskAction, b: RiskAction): RiskAction { + return SEVERITY[a] >= SEVERITY[b] ? a : b +} + +/** The less severe of two actions — used to clamp a verdict to what its source may cause. */ +export function clampAction(action: RiskAction, ceiling: RiskAction): RiskAction { + return SEVERITY[action] <= SEVERITY[ceiling] ? action : ceiling +} + +/** + * Score each label contributes. Contributions are summed per subject and capped at 100. + * + * Graph proximity is graded by distance: each hop roughly halves the weight, because every + * intermediary between the subject and the seed weakens what the edge proves. A 3-hop label on + * its own moves no action (25 < the delay threshold) — it exists to combine with other signals. + */ +export const LABEL_WEIGHTS: Record = { + sanctions: 100, // OFAC / OpenSanctions direct hit + sanctioned_mixer: 100, + scam_token: 100, // confirmed, not suspected + operator_deny: 100, + sanctions_1hop: 70, // subject sent TO a sanctioned address + fake_stablecoin_suspect: 65, + mixer_exposure: 60, + honeypot_suspect: 55, + contract_admin_risk: 50, + sanctions_2hop: 45, // one intermediary between the subject and a sanctioned address + sanctions_1hop_inbound: 40, // a sanctioned address sent TO the subject — see N_HOP + mixer_exposure_2hop: 35, + sanctions_3hop: 25, // two intermediaries — context that combines, never acts alone + sanctions_2hop_inbound: 20, + unverified_contract: 20, + mixer_exposure_3hop: 20, + upgradeable_proxy: 15, + sanctions_3hop_inbound: 10, +} + +/** + * Labels that assert the subject IS the thing, rather than that it is near one. + * + * Only these can drive an automatic refusal. Derived signals sum toward the score normally, but + * however high they stack they escalate to a human rather than blocking on their own: "1 hop from + * a sanctioned address AND exposed to a mixer" is strong evidence, not a confirmed hit, and + * refusing on it alone would freeze funds on an inference. + */ +export const DIRECT_HIT_LABELS: ReadonlySet = new Set([ + 'sanctions', + 'sanctioned_mixer', + 'scam_token', + 'operator_deny', +]) + +/** Lowest score selecting each action; evaluated highest-first. */ +export const ACTION_THRESHOLDS: ReadonlyArray = [ + [90, 'block'], + [60, 'manual-review'], + [30, 'delay'], + [0, 'allow'], +] + +/** The most severe action each enforcement level permits its source to cause. */ +const ENFORCEMENT_CEILING: Record = { + block: 'block', + manual_review_only: 'manual-review', + // Contributes to the score and can hold a packet for re-screening, but never demands a + // human decision or a refusal on its own. + score_only: 'delay', +} + +/** The most severe action a claim carrying only derived labels may cause. */ +const DERIVED_ONLY_CEILING: RiskAction = 'manual-review' + +/** + * `delay` re-evaluation. The total wait (40 min) deliberately exceeds the default RiskStore + * refresh interval (`DENYLIST_REFRESH_MS`, 30 min), so a deferred packet is always re-scored + * against at least one fresh feed before it escalates. + */ +export const DELAY_POLICY = { + retryAfterMs: 5 * 60_000, + maxAttempts: 8, + escalateTo: 'manual-review' as RiskAction, +} + +/** + * Graph traversal bounds. Depth is 3, computed by the external indexer (`graph/proximity.ts`) + * and delivered via the signed feed — the DVN itself never walks the graph. + * + * Direction is what makes this dusting-resistant. Anyone can push a tainted transfer at a + * victim, so INBOUND edges are weak evidence: every edge on an inbound path must clear the + * indexer's per-token minimum. An OUTBOUND path starts with the subject's own act (no + * threshold), but every edge after the first is someone else's — those must clear the minimum + * too, or anyone the subject ever paid could smear them by dusting a sanctioned address. + * + * A path counts only if funds could have flowed along it: hops stay on one chain, in + * non-decreasing block order, with no seed anywhere but the far endpoint. A token with no + * configured minimum records its edges but never extends a path. + */ +export const N_HOP = { + depth: 3, + outbound: { minValueEth: 0, labels: ['sanctions_1hop', 'sanctions_2hop', 'sanctions_3hop'] }, + inbound: { + minValueEth: 0.01, + labels: ['sanctions_1hop_inbound', 'sanctions_2hop_inbound', 'sanctions_3hop_inbound'], + }, +} + +/** + * One source's claim about a subject: the labels it applied, and optionally its own score. + * + * Grouping by source matters. `assertedScore` describes the subject as a whole, so it is worth + * counting once — not once per label, which would multiply it by however many labels happened to + * accompany it. + */ +export interface PolicyEntry { + source: LabelSource + labels: string[] + /** Source-asserted score for the subject; competes with the label sum rather than adding. */ + assertedScore?: number +} + +export interface PolicyDecision { + score: number + action: RiskAction + reasonCodes: string[] +} + +/** + * Score a subject's signals and choose an action. + * + * Two independent gates decide the outcome. The score, capped at 100, picks a candidate action + * from `ACTION_THRESHOLDS`. That candidate is then clamped to a ceiling, so a pile of + * `public_event` labels can total 100 and still only reach `delay`. + * + * The ceiling is computed **per claim** and the most permissive claim wins. Each claim is capped + * by both how far its source is trusted AND whether it actually asserts a direct hit. Pairing + * the two per claim is what closes the obvious hole: a `public_event` shouting "sanctions" next + * to an `ofac` entry carrying only derived labels must not combine into a block, because neither + * claim on its own is grounds for one. + * + * Each source contributes the greater of its label weights summed, or the score it asserted for + * the subject; those per-source figures then add up. A source that says "82" alongside three + * labels contributes 82 once, not 82 three times. + * + * Confidence deliberately does NOT scale the score. Weighting by it would silently shift + * outcomes off the agreed threshold table — a `trusted_indexer` 1-hop label would land on + * `delay` instead of the `manual-review` the policy calls for. Confidence is carried on the + * evidence for observability and for the indexer's own use. + */ +export function evaluate(entries: readonly PolicyEntry[]): PolicyDecision { + if (entries.length === 0) return { score: 0, action: 'allow', reasonCodes: [] } + + let score = 0 + let ceiling: RiskAction = 'allow' + const reasonCodes: string[] = [] + + for (const entry of entries) { + let labelSum = 0 + let hasDirectHit = false + for (const label of entry.labels) { + labelSum += LABEL_WEIGHTS[label] ?? 0 + if (DIRECT_HIT_LABELS.has(label)) hasDirectHit = true + if (!reasonCodes.includes(label)) reasonCodes.push(label) + } + score += Math.max(labelSum, entry.assertedScore ?? 0) + + // An asserted score cannot manufacture a direct hit: a source claiming 100 with only + // derived labels still tops out at manual-review. + const claimCeiling = clampAction( + ENFORCEMENT_CEILING[SOURCE_TRUST[entry.source].enforcement], + hasDirectHit ? 'block' : DERIVED_ONLY_CEILING, + ) + ceiling = worseAction(ceiling, claimCeiling) + } + score = Math.min(100, score) + + const candidate = ACTION_THRESHOLDS.find(([min]) => score >= min)?.[1] ?? 'allow' + return { score, action: clampAction(candidate, ceiling), reasonCodes } +} diff --git a/worker/assess/providers/contract.ts b/worker/assess/providers/contract.ts new file mode 100644 index 0000000..996ee3e --- /dev/null +++ b/worker/assess/providers/contract.ts @@ -0,0 +1,141 @@ +/** + * Contract risk provider — the on-chain facts about an address that bear on risk. + * + * Scope is deliberately what a node can read for itself: whether the address holds code, + * whether it sits behind an upgradeable proxy, and who controls it. Source-verification status + * is NOT here — it cannot be observed on-chain and belongs to the indexer feed. + * + * Every lookup is cached and time-bounded. The worker blocks on this per packet, so an + * unresponsive RPC must degrade the decision (the caller turns a failure into a hold), never + * stall the scan loop. + */ + +/** The slice of a chain provider this needs. Kept tiny so tests stay offline. */ +export interface ChainReader { + getCode(address: string): Promise + getStorageAt(address: string, slot: string): Promise + call(tx: { to: string; data: string }): Promise +} + +/** EIP-1967 standard slots: keccak256("eip1967.proxy.") - 1. */ +const SLOT_IMPLEMENTATION = '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' +const SLOT_ADMIN = '0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103' + +const SELECTOR_OWNER = '0x8da5cb5b' // owner() +const SELECTOR_ADMIN = '0xf851a440' // admin() + +const ZERO = '0x0000000000000000000000000000000000000000' + +export interface ContractFacts { + isContract: boolean + /** An EIP-1967 implementation slot is set, so the code behind this address can change. */ + proxy: boolean + implementation?: string + /** Whoever the contract itself names as owner/admin, if it exposes one. */ + controller?: string +} + +export interface ContractInspector { + inspect(subject: string, chainKey: string): Promise +} + +export interface ContractInspectorOptions { + readers: Record + /** Per-inspection budget. Exceeding it rejects, which the caller turns into a hold. */ + timeoutMs?: number + /** How long facts stay cached. Upgrades are rare; a few minutes is plenty. */ + cacheTtlMs?: number + maxCacheEntries?: number + now?: () => number +} + +/** Read a 32-byte word as an address, or undefined when the slot is empty. */ +function wordToAddress(word: string): string | undefined { + if (!word || word === '0x') return undefined + const hex = word.replace(/^0x/, '').padStart(64, '0') + const addr = '0x' + hex.slice(24) + return addr === ZERO ? undefined : addr.toLowerCase() +} + +function withTimeout(p: Promise, ms: number, what: string): Promise { + return new Promise((resolve, reject) => { + const timer = setTimeout(() => reject(new Error(`${what} timed out after ${ms}ms`)), ms) + p.then( + (v) => { + clearTimeout(timer) + resolve(v) + }, + (e) => { + clearTimeout(timer) + reject(e) + }, + ) + }) +} + +interface CacheEntry { + facts: ContractFacts + expiresAt: number +} + +export class RpcContractInspector implements ContractInspector { + private cache = new Map() + private readonly timeoutMs: number + private readonly cacheTtlMs: number + private readonly maxCacheEntries: number + private readonly now: () => number + + constructor(private readonly opts: ContractInspectorOptions) { + this.timeoutMs = opts.timeoutMs ?? 3000 + this.cacheTtlMs = opts.cacheTtlMs ?? 300_000 + this.maxCacheEntries = opts.maxCacheEntries ?? 5000 + this.now = opts.now ?? Date.now + } + + async inspect(subject: string, chainKey: string): Promise { + const address = subject.toLowerCase() + const key = `${chainKey}:${address}` + const cached = this.cache.get(key) + if (cached && cached.expiresAt > this.now()) return cached.facts + + const reader = this.opts.readers[chainKey] + // No reader for this chain means we cannot make a claim. Say so rather than reporting a + // clean EOA, which would read as "checked and fine". + if (!reader) throw new Error(`no chain reader configured for '${chainKey}'`) + + const facts = await withTimeout(this.read(reader, address), this.timeoutMs, `contract inspect ${key}`) + this.remember(key, facts) + return facts + } + + private async read(reader: ChainReader, address: string): Promise { + const code = await reader.getCode(address) + if (!code || code === '0x') return { isContract: false, proxy: false } + + // Proxy detection and controller discovery are independent; neither should mask the other. + const [implementation, slotAdmin, owner, adminFn] = await Promise.all([ + reader.getStorageAt(address, SLOT_IMPLEMENTATION).then(wordToAddress, () => undefined), + reader.getStorageAt(address, SLOT_ADMIN).then(wordToAddress, () => undefined), + reader.call({ to: address, data: SELECTOR_OWNER }).then(wordToAddress, () => undefined), + reader.call({ to: address, data: SELECTOR_ADMIN }).then(wordToAddress, () => undefined), + ]) + + return { + isContract: true, + proxy: implementation !== undefined, + implementation, + // Prefer the proxy admin: on an upgradeable contract it is the address that can swap the + // code out, which outranks whatever the implementation calls its owner. + controller: slotAdmin ?? adminFn ?? owner, + } + } + + private remember(key: string, facts: ContractFacts): void { + if (this.cache.size >= this.maxCacheEntries) { + // Cheap eviction: drop the oldest insertion. Map preserves insertion order. + const oldest = this.cache.keys().next().value + if (oldest !== undefined) this.cache.delete(oldest) + } + this.cache.set(key, { facts, expiresAt: this.now() + this.cacheTtlMs }) + } +} diff --git a/worker/assess/providers/token.ts b/worker/assess/providers/token.ts new file mode 100644 index 0000000..f832982 --- /dev/null +++ b/worker/assess/providers/token.ts @@ -0,0 +1,234 @@ +import type { ChainReader } from './contract' + +/** + * Token risk provider — resolves the ERC-20 an OFT actually moves, and reads the static + * metadata needed to spot an impersonating token. + * + * Curated scam/phishing token labels are NOT here; they live in the `RiskStore` like every + * other list-based signal (see `ingest/tokens.ts`). This provider only supplies what has to be + * read from the chain at decision time. Honeypot simulation is deliberately out of scope. + */ + +const SELECTOR_TOKEN = '0xfc0c546a' // token() +const SELECTOR_SYMBOL = '0x95d89b41' // symbol() +const SELECTOR_DECIMALS = '0x313ce567' // decimals() + +const ZERO = '0x0000000000000000000000000000000000000000' + +/** + * What we could establish about the OApp's underlying token. + * + * `not-a-token` and `unknown` are kept apart on purpose. A plain OApp that does not implement + * `token()` reverts, and that is a definite answer. An RPC that times out is not — collapsing + * the two would let an outage silently skip token screening on every packet. + */ +export type TokenResolution = + | { kind: 'token'; address: string } + | { kind: 'not-a-token' } + | { kind: 'unknown'; reason: string } + +export interface TokenFacts { + address: string + symbol?: string + decimals?: number +} + +export interface TokenInspector { + resolveToken(oapp: string, chainKey: string): Promise + inspect(token: string, chainKey: string): Promise +} + +export interface TokenInspectorOptions { + readers: Record + timeoutMs?: number + cacheTtlMs?: number + maxCacheEntries?: number + now?: () => number +} + +/** + * Whether a failed `eth_call` was the contract refusing (a definite "no such function") rather + * than the transport failing. ethers tags reverts as CALL_EXCEPTION; the message check covers + * providers that only surface a string. + */ +export function isRevert(err: unknown): boolean { + const e = err as { code?: string; message?: string } + if (e?.code === 'CALL_EXCEPTION') return true + return /revert|invalid opcode|execution reverted|function selector was not recognized/i.test(e?.message ?? '') +} + +function wordToAddress(word: string): string | undefined { + if (!word || word === '0x') return undefined + const hex = word.replace(/^0x/, '').padStart(64, '0') + const addr = '0x' + hex.slice(24) + return addr === ZERO ? undefined : addr.toLowerCase() +} + +/** + * Decode a `symbol()` return value. Modern tokens return a dynamic `string`; a handful of early + * ones return a fixed `bytes32`, so both shapes are handled. + */ +export function decodeStringReturn(data: string): string | undefined { + const hex = data.replace(/^0x/, '') + if (hex.length === 0) return undefined + + // Dynamic string: offset word (0x20) + length word + padded bytes. + if (hex.length >= 192 && BigInt('0x' + hex.slice(0, 64)) === 32n) { + const len = Number(BigInt('0x' + hex.slice(64, 128))) + if (len === 0 || len > 128) return undefined + const bytes = hex.slice(128, 128 + len * 2) + if (bytes.length < len * 2) return undefined + return Buffer.from(bytes, 'hex').toString('utf8').replace(/\0+$/, '') || undefined + } + + // bytes32: trailing zero padding. + if (hex.length === 64) { + const trimmed = hex.replace(/(00)+$/, '') + if (!trimmed) return undefined + return Buffer.from(trimmed, 'hex').toString('utf8').replace(/[^\x20-\x7e]/g, '') || undefined + } + return undefined +} + +function decodeUint8(data: string): number | undefined { + const hex = data.replace(/^0x/, '') + if (hex.length === 0) return undefined + const n = Number(BigInt('0x' + hex.slice(0, 64))) + return n >= 0 && n <= 255 ? n : undefined +} + +function withTimeout(p: Promise, ms: number, what: string): Promise { + return new Promise((resolve, reject) => { + const timer = setTimeout(() => reject(new Error(`${what} timed out after ${ms}ms`)), ms) + p.then( + (v) => { + clearTimeout(timer) + resolve(v) + }, + (e) => { + clearTimeout(timer) + reject(e) + }, + ) + }) +} + +export class RpcTokenInspector implements TokenInspector { + private resolutions = new Map() + private facts = new Map() + private readonly timeoutMs: number + private readonly cacheTtlMs: number + private readonly maxCacheEntries: number + private readonly now: () => number + + constructor(private readonly opts: TokenInspectorOptions) { + this.timeoutMs = opts.timeoutMs ?? 3000 + this.cacheTtlMs = opts.cacheTtlMs ?? 300_000 + this.maxCacheEntries = opts.maxCacheEntries ?? 5000 + this.now = opts.now ?? Date.now + } + + async resolveToken(oapp: string, chainKey: string): Promise { + const key = `${chainKey}:${oapp.toLowerCase()}` + const cached = this.resolutions.get(key) + // Never cache `unknown`: it is a transport failure, not a fact about the contract. + if (cached && cached.expiresAt > this.now()) return cached.value + + const reader = this.opts.readers[chainKey] + if (!reader) return { kind: 'unknown', reason: `no chain reader configured for '${chainKey}'` } + + let resolution: TokenResolution + try { + const data = await withTimeout( + reader.call({ to: oapp.toLowerCase(), data: SELECTOR_TOKEN }), + this.timeoutMs, + `token() on ${key}`, + ) + const address = wordToAddress(data) + // An OFT whose token() returns nothing useful is not something we can screen. + resolution = address ? { kind: 'token', address } : { kind: 'not-a-token' } + } catch (err) { + if (!isRevert(err)) return { kind: 'unknown', reason: (err as Error).message } + resolution = { kind: 'not-a-token' } + } + + remember(this.resolutions, key, resolution, this.now() + this.cacheTtlMs, this.maxCacheEntries) + return resolution + } + + async inspect(token: string, chainKey: string): Promise { + const address = token.toLowerCase() + const key = `${chainKey}:${address}` + const cached = this.facts.get(key) + if (cached && cached.expiresAt > this.now()) return cached.value + + const reader = this.opts.readers[chainKey] + if (!reader) throw new Error(`no chain reader configured for '${chainKey}'`) + + // Metadata is optional per ERC-20, so a reverting symbol()/decimals() is not a failure. + const [symbolData, decimalsData] = await withTimeout( + Promise.all([ + reader.call({ to: address, data: SELECTOR_SYMBOL }).catch(() => '0x'), + reader.call({ to: address, data: SELECTOR_DECIMALS }).catch(() => '0x'), + ]), + this.timeoutMs, + `token metadata ${key}`, + ) + + const value: TokenFacts = { + address, + symbol: decodeStringReturn(symbolData), + decimals: decodeUint8(decimalsData), + } + remember(this.facts, key, value, this.now() + this.cacheTtlMs, this.maxCacheEntries) + return value + } +} + +function remember(cache: Map, key: string, value: T, expiresAt: number, max: number): void { + if (cache.size >= max) { + const oldest = cache.keys().next().value + if (oldest !== undefined) cache.delete(oldest) + } + cache.set(key, { value, expiresAt }) +} + +/** + * Symbols worth impersonating. A token claiming one of these while sitting at an address other + * than the chain's canonical one is the fake-stablecoin pattern. + */ +export const WATCHED_STABLE_SYMBOLS = new Set(['USDC', 'USDT', 'DAI', 'BUSD', 'PYUSD', 'FDUSD', 'USDE']) + +/** + * Canonical stablecoin addresses per chain, lowercased. + * + * IMPORTANT: an entry that is wrong or out of date makes the REAL token look like an + * impersonator (a `manual-review` false positive, never a block). A symbol with no entry for the + * chain is simply not judged — so leaving a chain out is safe, while guessing is not. Verify + * every address against the issuer before adding one. + */ +export const CANONICAL_STABLECOINS: Record> = { + baseSepolia: { + // Circle's official Base Sepolia USDC. Re-verify against Circle's docs before relying on it. + USDC: '0x036cbd53842c5426634e7929541ec2318f3dcf7e', + }, + optimismSepolia: { + // Circle's official OP Sepolia USDC. Re-verify against Circle's docs before relying on it. + USDC: '0x5fd84259d66cd46123540766be93dfe6d43130d7', + }, +} + +/** + * Whether `facts` describe a token impersonating a major stablecoin on `chainKey`. + * + * Returns false when the symbol is not watched, or when we hold no canonical address for that + * symbol on that chain — an unknown pairing is not evidence of anything. + */ +export function isFakeStablecoin(facts: TokenFacts, chainKey: string): boolean { + if (!facts.symbol) return false + const symbol = facts.symbol.trim().toUpperCase() + if (!WATCHED_STABLE_SYMBOLS.has(symbol)) return false + const canonical = CANONICAL_STABLECOINS[chainKey]?.[symbol] + if (!canonical) return false + return canonical !== facts.address.toLowerCase() +} diff --git a/worker/assess/sources.ts b/worker/assess/sources.ts new file mode 100644 index 0000000..95f6df0 --- /dev/null +++ b/worker/assess/sources.ts @@ -0,0 +1,45 @@ +/** + * Label sources and how far each one is trusted to push an enforcement action. + * + * Not every signal deserves the same authority. A direct OFAC match may block a transfer + * outright; a label scraped from a public event may only move the score. `SOURCE_TRUST` is + * the single place that distinction lives — the policy engine reads it and caps each piece + * of evidence accordingly, so a permissive source can never be escalated by accident. + */ + +export type LabelSource = + | 'ofac' + | 'opensanctions' + | 'operator' + | 'trusted_indexer' + | 'own_verdict_event' + | 'public_event' + +/** The most severe action a source's evidence is allowed to cause on its own. */ +export type EnforcementLevel = 'block' | 'manual_review_only' | 'score_only' + +export interface SourceTrust { + enforcement: EnforcementLevel + /** Default confidence for entries this source asserts, when it states none itself. */ + confidence: number +} + +export const SOURCE_TRUST: Record = { + // Authoritative sanctions lists: a direct hit is grounds to refuse outright. + ofac: { enforcement: 'block', confidence: 1 }, + opensanctions: { enforcement: 'block', confidence: 1 }, + // Operator's own denylist — blocking is an explicit operational choice. + operator: { enforcement: 'block', confidence: 1 }, + // Indexer feeds are only admitted after signature + allowlist verification, so they may + // block; the lower confidence reflects derived (graph) rather than asserted labels. + trusted_indexer: { enforcement: 'block', confidence: 0.8 }, + // Our own past verdicts: useful for propagation and audit, but self-reinforcing. Capped + // so a stale verdict of ours can never harden into an automatic block. + own_verdict_event: { enforcement: 'manual_review_only', confidence: 0.6 }, + // Anyone can emit an event. Contributes to the score, never drives an automatic refusal. + public_event: { enforcement: 'score_only', confidence: 0.3 }, +} + +export function isLabelSource(s: string): s is LabelSource { + return s in SOURCE_TRUST +} diff --git a/worker/assess/store.ts b/worker/assess/store.ts index 4a5a640..952ab42 100644 --- a/worker/assess/store.ts +++ b/worker/assess/store.ts @@ -1,29 +1,138 @@ -export interface DenyEntry { address: string; tags: string[]; reasons: string[] } +import { SOURCE_TRUST, type LabelSource } from './sources' -export class Denylist { - private map = new Map() +export type SubjectType = 'address' | 'contract' | 'token' + +/** + * One source's assertion about one subject. + * + * Entries are per-source rather than one merged record per subject, and deliberately so: a + * feed entry that expires in an hour must not carry an OFAC label to the grave with it. + * Keeping each source's claim separate means TTL, confidence, and trust stay attached to the + * claim they describe. + */ +export interface RiskEntry { + subject: string + subjectType: SubjectType + labels: string[] + source: LabelSource + confidence: number + /** Source-asserted score, if the source states one (indexer feeds do; OFAC does not). */ + score?: number + firstSeen: number + lastSeen: number + /** Epoch ms after which this entry is ignored. Absent means it never expires. */ + expiresAt?: number + /** Hash of the off-chain evidence document backing this entry. */ + evidenceHash?: string +} + +export interface RiskInput { + subject: string + subjectType?: SubjectType + labels: string[] + source: LabelSource + confidence?: number + score?: number + expiresAt?: number + evidenceHash?: string +} + +export interface RiskStoreOptions { + now?: () => number +} + +/** + * The worker's local risk cache — not contract storage, and not a long-term database. It + * holds only what the current screening decision needs, keyed by lowercased subject. + */ +export class RiskStore { + private bySubject = new Map() + private readonly now: () => number + + constructor(opts: RiskStoreOptions = {}) { + this.now = opts.now ?? Date.now + } + + /** + * Record a source's assertion. Re-asserting the same subject from the same source merges + * labels and refreshes `lastSeen`, TTL, and confidence — a refreshed feed should extend an + * entry's life, not accumulate duplicates of it. + */ + upsert(input: RiskInput): RiskEntry { + const subject = input.subject.toLowerCase() + const t = this.now() + const entries = this.bySubject.get(subject) ?? [] + const existing = entries.find((e) => e.source === input.source) - add(address: string, tag: string, reason: string): void { - const key = address.toLowerCase() - const existing = this.map.get(key) if (existing) { - if (!existing.tags.includes(tag)) existing.tags.push(tag) - existing.reasons.push(reason) - } else { - this.map.set(key, { address: key, tags: [tag], reasons: [reason] }) + for (const label of input.labels) { + if (!existing.labels.includes(label)) existing.labels.push(label) + } + existing.lastSeen = t + existing.expiresAt = input.expiresAt + if (input.subjectType) existing.subjectType = input.subjectType + if (input.confidence !== undefined) existing.confidence = input.confidence + if (input.score !== undefined) existing.score = input.score + if (input.evidenceHash !== undefined) existing.evidenceHash = input.evidenceHash + return existing } + + const entry: RiskEntry = { + subject, + subjectType: input.subjectType ?? 'address', + labels: [...input.labels], + source: input.source, + confidence: input.confidence ?? SOURCE_TRUST[input.source].confidence, + score: input.score, + firstSeen: t, + lastSeen: t, + expiresAt: input.expiresAt, + evidenceHash: input.evidenceHash, + } + entries.push(entry) + this.bySubject.set(subject, entries) + return entry + } + + /** Live entries for a subject. Expired entries are filtered out, not returned as clean. */ + lookup(subject: string): RiskEntry[] { + const entries = this.bySubject.get(subject.toLowerCase()) + if (!entries) return [] + const t = this.now() + return entries.filter((e) => e.expiresAt === undefined || e.expiresAt > t) + } + + /** Whether any live entry exists for this subject. */ + has(subject: string): boolean { + return this.lookup(subject).length > 0 } - has(address: string): boolean { return this.map.has(address.toLowerCase()) } - lookup(address: string): DenyEntry | undefined { return this.map.get(address.toLowerCase()) } - get size(): number { return this.map.size } + /** Distinct subjects holding at least one live entry. */ + get size(): number { + let n = 0 + for (const subject of this.bySubject.keys()) if (this.has(subject)) n++ + return n + } - /** Count of entries carrying each tag (an address may count toward several tags). */ - tagCounts(): Record { + /** Live entry count per source, for the denylist-size gauge. */ + countsBySource(): Record { const counts: Record = {} - for (const e of this.map.values()) { - for (const t of e.tags) counts[t] = (counts[t] ?? 0) + 1 + for (const subject of this.bySubject.keys()) { + for (const e of this.lookup(subject)) counts[e.source] = (counts[e.source] ?? 0) + 1 } return counts } + + /** Drop expired entries. Returns how many were removed. */ + prune(): number { + const t = this.now() + let removed = 0 + for (const [subject, entries] of this.bySubject) { + const live = entries.filter((e) => e.expiresAt === undefined || e.expiresAt > t) + removed += entries.length - live.length + if (live.length) this.bySubject.set(subject, live) + else this.bySubject.delete(subject) + } + return removed + } } diff --git a/worker/assess/testDenylist.ts b/worker/assess/testDenylist.ts index b79c3a0..956e80b 100644 --- a/worker/assess/testDenylist.ts +++ b/worker/assess/testDenylist.ts @@ -1,14 +1,17 @@ -import { Denylist } from './store' +import { RiskStore } from './store' const isEvmAddress = (s: string) => /^0x[0-9a-fA-F]{40}$/.test(s) /** Operator-controlled flagged addresses (keys we hold) so a live blocked transfer is demoable. * Source: TEST_DENYLIST env var, comma-separated. */ -export function loadTestDenylist(dl: Denylist, csv = process.env.TEST_DENYLIST || ''): number { +export function loadTestDenylist(store: RiskStore, csv = process.env.TEST_DENYLIST || ''): number { let n = 0 for (const raw of csv.split(',')) { const a = raw.trim().toLowerCase() - if (isEvmAddress(a)) { dl.add(a, 'test', 'Operator test denylist entry'); n++ } + if (isEvmAddress(a)) { + store.upsert({ subject: a, subjectType: 'address', labels: ['operator_deny'], source: 'operator' }) + n++ + } } return n } diff --git a/worker/assess/verdict.ts b/worker/assess/verdict.ts new file mode 100644 index 0000000..16ec889 --- /dev/null +++ b/worker/assess/verdict.ts @@ -0,0 +1,185 @@ +import { ethers } from 'ethers' +import { canonicalize } from './canonical' +import { POLICY_VERSION, type RiskAction } from './policy' +import type { Assessment } from './assess' + +/** + * On-chain encoding of a risk verdict. + * + * The contract stores nothing; it only emits. So these encodings ARE the audit trail, and an + * indexer decoding a two-year-old log depends on them still meaning the same thing. Both the + * action codes and the reason-bit assignments below are therefore permanent: append, never + * renumber or reuse. + */ + +/** Must match `ACTION_*` in ComplianceDVN.sol. */ +export const ACTION_CODES: Record = { + allow: 0, + delay: 1, + 'manual-review': 2, + block: 3, +} + +/** + * Reason code -> bit position in the `reasonMask` uint256. + * + * APPEND-ONLY. Renumbering a bit silently rewrites the meaning of every event already emitted, + * so a new reason takes the next free index and retired ones are left in place. + */ +export const REASON_BITS: Record = { + // Direct hits + sanctions: 0, + sanctioned_mixer: 1, + scam_token: 2, + operator_deny: 3, + // Graph-derived + sanctions_1hop: 4, + sanctions_1hop_inbound: 5, + mixer_exposure: 6, + // Token + fake_stablecoin_suspect: 7, + honeypot_suspect: 8, + // Contract + contract_admin_risk: 9, + unverified_contract: 10, + upgradeable_proxy: 11, + // Operational + contract_check_unavailable: 12, + token_check_unavailable: 13, + /** Set when a held packet was released by an owner approval rather than by re-screening. */ + owner_approved: 14, + // Graph-derived, depth 2-3 (policy v2) + sanctions_2hop: 15, + sanctions_3hop: 16, + sanctions_2hop_inbound: 17, + sanctions_3hop_inbound: 18, + mixer_exposure_2hop: 19, + mixer_exposure_3hop: 20, +} + +/** Reserved top bit, set when a reason code has no assigned bit — so nothing vanishes silently. */ +export const UNMAPPED_REASON_BIT = 255 + +export interface OnChainVerdict { + action: number + score: number + reasonMask: bigint + evidenceHash: string +} + +/** + * Pack reason codes into a bitmask. + * + * A code with no assigned bit sets `UNMAPPED_REASON_BIT` instead of being dropped: an audit + * record that quietly omits a reason is worse than one that says "there was a reason I cannot + * name". Returns the unmapped codes so the caller can log them. + */ +export function reasonMask(codes: readonly string[]): { mask: bigint; unmapped: string[] } { + let mask = 0n + const unmapped: string[] = [] + for (const code of codes) { + const bit = REASON_BITS[code] + if (bit === undefined) { + unmapped.push(code) + mask |= 1n << BigInt(UNMAPPED_REASON_BIT) + } else { + mask |= 1n << BigInt(bit) + } + } + return { mask, unmapped } +} + +/** Decode a mask back to reason codes. Used by tests and available to the indexer. */ +export function decodeReasonMask(mask: bigint): string[] { + const byBit = new Map(Object.entries(REASON_BITS).map(([code, bit]) => [bit, code])) + const out: string[] = [] + for (let bit = 0; bit <= UNMAPPED_REASON_BIT; bit++) { + if ((mask >> BigInt(bit)) & 1n) out.push(byBit.get(bit) ?? `unmapped:${bit}`) + } + return out +} + +/** One scored signal, as committed to by `evidenceHash`. */ +export interface EvidenceRecord { + type: string + weight: number + /** Confidence as an integer percent — the document must canonicalize, and floats cannot. */ + confidencePct: number + source: string + subject: string +} + +/** + * The off-chain document `evidenceHash` commits to. + * + * It covers the verdict and the scored evidence that produced it. Free-form `details` on the + * internal evidence (a controller address, a decoded symbol) are deliberately excluded: they + * hold arbitrary values, and a future provider adding a float there would break hashing at + * runtime. Everything committed here is either derived from chain state the indexer can re-read + * independently, or from the feed it published itself. + */ +export interface EvidenceDocument { + policyVersion: number + payloadHash: string + action: RiskAction + score: number + reasonCodes: string[] + parties: Array<{ subject: string; chainKey: string }> + evidence: EvidenceRecord[] +} + +export function buildEvidenceDocument( + payloadHash: string, + verdict: Assessment, + parties: ReadonlyArray<{ subject: string; chainKey: string }>, +): EvidenceDocument { + return { + policyVersion: POLICY_VERSION, + payloadHash: payloadHash.toLowerCase(), + action: verdict.action, + score: verdict.score, + reasonCodes: [...verdict.reasonCodes], + parties: parties.map((p) => ({ subject: p.subject.toLowerCase(), chainKey: p.chainKey })), + evidence: verdict.evidence.map((e) => ({ + type: e.type, + weight: e.weight, + confidencePct: Math.round(e.confidence * 100), + source: e.source, + subject: e.subject, + })), + } +} + +export function evidenceHash(doc: EvidenceDocument): string { + return ethers.utils.keccak256(ethers.utils.toUtf8Bytes(canonicalize(doc))) +} + +/** + * Encode a verdict for the chain. + * + * `overrideAction` exists for the owner-approved release: the action taken was `allow` (a human + * allowed it) even though re-screening still scores it `manual-review`, and the contract refuses + * to accept a verification claiming anything but allow. The reason mask still carries why it had + * been held, plus `owner_approved`. + */ +export function encodeVerdict( + payloadHash: string, + verdict: Assessment, + parties: ReadonlyArray<{ subject: string; chainKey: string }>, + opts: { overrideAction?: RiskAction; extraReasons?: string[] } = {}, +): { encoded: OnChainVerdict; unmapped: string[] } { + const action = opts.overrideAction ?? verdict.action + const codes = [...verdict.reasonCodes, ...(opts.extraReasons ?? [])] + const { mask, unmapped } = reasonMask(codes) + const doc = buildEvidenceDocument(payloadHash, { ...verdict, action, reasonCodes: codes }, parties) + return { + encoded: { + action: ACTION_CODES[action], + // uint16 on-chain; the policy caps the score at 100 but clamp rather than overflow. + score: Math.max(0, Math.min(65535, Math.round(verdict.score))), + reasonMask: mask, + evidenceHash: evidenceHash(doc), + }, + unmapped, + } +} diff --git a/worker/chain/events.ts b/worker/chain/events.ts index f5c8a95..4a44771 100644 --- a/worker/chain/events.ts +++ b/worker/chain/events.ts @@ -39,24 +39,46 @@ export const ENDPOINT_ABI = [ 'event PacketSent(bytes encodedPayload, bytes options, address sendLibrary)', ] -/** Scan a block range on the source endpoint for PacketSent and return parsed packets. */ +/** + * Scan a block range on the source endpoint for PacketSent and return parsed packets. + * + * The endpoint is shared by every OApp on the chain, so most of what this sees belongs to + * strangers — arbitrary message shapes, not OFT transfers. A packet we cannot decode is + * therefore expected traffic, not an error, and is skipped rather than thrown: letting one + * foreign log escape would abort the whole chain scan, and because the checkpoint is frozen on + * failure the same log would abort it again forever. + * + * Skipping is safe in the fail-closed sense — withholding verification IS the veto, so a packet + * we never parsed is also a packet we never let through. `onSkip` exists so it is still counted + * and visible rather than silently dropped. + */ export async function scanPacketSent( provider: ethers.providers.Provider, endpoint: string, fromBlock: number, toBlock: number, + onSkip?: (payloadHash: string, reason: string) => void, ): Promise { const iface = new ethers.utils.Interface(ENDPOINT_ABI) const topic = iface.getEventTopic('PacketSent') const logs = await provider.getLogs({ address: endpoint, topics: [topic], fromBlock, toBlock }) - return logs.map((l) => { - const decoded = iface.decodeEventLog('PacketSent', l.data, l.topics) - return parseEncodedPacket(decoded.encodedPayload as string) - }) + const packets: ParsedPacket[] = [] + for (const l of logs) { + const encoded = stripHex(iface.decodeEventLog('PacketSent', l.data, l.topics).encodedPayload as string) + try { + packets.push(parseEncodedPacket('0x' + encoded)) + } catch (err) { + // The payload hash needs only the split, so an undecodable packet is still identifiable. + const payloadHash = ethers.utils.keccak256('0x' + encoded.slice(81 * 2)) + onSkip?.(payloadHash, (err as Error).message) + } + } + return packets } export const DVN_EVENT_ABI = [ 'event JobAssigned(uint32 dstEid, bytes32 payloadHash, uint64 confirmations, address sender)', + 'event PacketApproved(bytes32 indexed payloadHash, address approver)', ] /** Pure decode: extract the (lowercased) payloadHash from a JobAssigned log. */ @@ -81,3 +103,25 @@ export async function scanJobAssigned( const logs = await provider.getLogs({ address: dvnAddress, topics: [topic], fromBlock, toBlock }) return new Set(logs.map((l) => decodeJobAssignedPayloadHash(iface, l.data, l.topics))) } + +/** + * Scan our ComplianceDVN for owner approvals of held packets. + * + * `payloadHash` is indexed, so it is read from topics rather than data. Approvals are recorded + * on the DVN that will submit the verification — the destination chain's — but the worker + * scans every configured chain and matches purely on payloadHash, so it does not need to know + * which side an approval arrived on. + */ +export async function scanPacketApproved( + provider: ethers.providers.Provider, + dvnAddress: string, + fromBlock: number, + toBlock: number, +): Promise> { + const iface = new ethers.utils.Interface(DVN_EVENT_ABI) + const topic = iface.getEventTopic('PacketApproved') + const logs = await provider.getLogs({ address: dvnAddress, topics: [topic], fromBlock, toBlock }) + return new Set( + logs.map((l) => (iface.decodeEventLog('PacketApproved', l.data, l.topics).payloadHash as string).toLowerCase()), + ) +} diff --git a/worker/chain/reader.ts b/worker/chain/reader.ts new file mode 100644 index 0000000..4e035b1 --- /dev/null +++ b/worker/chain/reader.ts @@ -0,0 +1,16 @@ +import { ethers } from 'ethers' +import type { ChainReader } from '../assess/providers/contract' + +/** + * Adapt an ethers provider to the narrow `ChainReader` the contract risk provider needs. + * + * The provider module stays ethers-free so it can be unit-tested with plain stubs; this is the + * one place the two meet. + */ +export function ethersReader(provider: ethers.providers.Provider): ChainReader { + return { + getCode: (address) => provider.getCode(address), + getStorageAt: (address, slot) => provider.getStorageAt(address, slot), + call: (tx) => provider.call(tx), + } +} diff --git a/worker/chain/verify.ts b/worker/chain/verify.ts deleted file mode 100644 index 6ad72b0..0000000 --- a/worker/chain/verify.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { ethers } from 'ethers' - -const DVN_ABI = [ - 'function submitVerification(bytes packetHeader, bytes32 payloadHash, uint64 confirmations) external', -] - -const RECEIVE_ULN_ABI = [ - 'function commitVerification(bytes packetHeader, bytes32 payloadHash) external', -] - -export async function submitVerification( - signer: ethers.Signer, - dvnAddress: string, - packetHeader: string, - payloadHash: string, - confirmations: number, -): Promise { - const dvn = new ethers.Contract(dvnAddress, DVN_ABI, signer) - const tx = await dvn.submitVerification(packetHeader, payloadHash, confirmations) - const receipt = await tx.wait() - return receipt.transactionHash -} - -/** - * Commit the verification on the destination ReceiveUln302 (permissionless). - * In production the LayerZero executor calls this once required DVNs verify, but the - * default executor does not track custom DVNs — so our worker drives it. Once committed, - * the executor performs lzReceive and the message is delivered. - */ -export async function commitVerification( - signer: ethers.Signer, - receiveUln: string, - packetHeader: string, - payloadHash: string, -): Promise { - const uln = new ethers.Contract(receiveUln, RECEIVE_ULN_ABI, signer) - const tx = await uln.commitVerification(packetHeader, payloadHash) - const receipt = await tx.wait() - return receipt.transactionHash -} diff --git a/worker/checkpoint.ts b/worker/checkpoint.ts index 27176b6..05c483d 100644 --- a/worker/checkpoint.ts +++ b/worker/checkpoint.ts @@ -1,10 +1,58 @@ import { readFileSync, writeFileSync, renameSync, existsSync, mkdirSync } from 'fs' import { dirname } from 'path' -interface PersistedState { lastBlock: Record; processed: string[] } +/** Actions that leave a packet unresolved, so it must be reconsidered later. */ +export type DeferredAction = 'delay' | 'manual-review' /** - * Crash-safe persistence of scan progress (last block per chain) and processed packet keys. + * One screened party and the chain its state lives on. A packet's parties are split across + * chains — the sender is on the source, the receiver and OFT recipient on the destination — so + * the chain cannot be inferred when the packet is re-screened later. + */ +export interface Party { + subject: string + chainKey: string +} + +/** + * A packet held back rather than decided. It carries everything needed to re-screen and then + * verify it later, because by the time it is reconsidered the source block has long fallen + * behind the scan cursor and cannot be re-read. + */ +export interface DeferredRecord { + payloadHash: string + dstEid: number + header: string + /** + * The pieces `lzReceive` needs. Optional because records written before the worker drove delivery + * do not have them — such a packet is still verified and committed, just not delivered by us. + */ + guid?: string + message?: string + /** Source chain the packet came from — the deferred queue is scanned across all chains. */ + srcChainKey: string + /** Parties re-screened on every re-evaluation: sender, receiver, OFT recipient. */ + parties: Party[] + action: DeferredAction + score: number + reasonCodes: string[] + attempts: number + /** Epoch ms before which a `delay` record is not reconsidered. Unused for manual-review. */ + retryAfter: number + firstDeferredAt: number +} + +interface PersistedState { + lastBlock: Record + processed: string[] + deferred?: Record + approvals?: string[] + feedVersions?: Record +} + +/** + * Crash-safe persistence of scan progress (last block per chain), processed packet keys, the + * deferred queue, and observed on-chain approvals. * * Writes are atomic: we write to a sibling `.tmp` file and `rename` it into place. POSIX * rename is atomic, so a crash mid-write leaves the previous good file intact rather than a @@ -13,22 +61,83 @@ interface PersistedState { lastBlock: Record; processed: string[ export class Checkpoint { private lastBlock: Record = {} private processedSet = new Set() + private deferredMap = new Map() + private approvalSet = new Set() + private feedVersions: Record = {} constructor(private path: string) { if (existsSync(path)) { const s = JSON.parse(readFileSync(path, 'utf8')) as PersistedState this.lastBlock = s.lastBlock || {} this.processedSet = new Set(s.processed || []) + this.deferredMap = new Map(Object.entries(s.deferred || {})) + this.approvalSet = new Set(s.approvals || []) + this.feedVersions = s.feedVersions || {} } } + /** + * Highest accepted feed version per source. Persisted so a restart cannot be handed an older + * feed that is still correctly signed and unexpired — replay protection has to outlive the + * process to be worth anything. + */ + getFeedVersion(source: string): number { + return this.feedVersions[source] ?? 0 + } + + setFeedVersion(source: string, version: number): void { + this.feedVersions[source] = version + } + + /** + * Upper bound on remembered processed keys. This set is a dedupe cache, not a ledger — the + * on-chain events are the ledger. A settled packet can only be presented again by manually + * rewinding the block cursor, and a rewind reaching keys old enough to have been evicted + * costs a redundant (idempotent) re-verification, not a wrong decision. Unbounded, the set + * would grow with every packet ever screened and be rewritten to disk on each save. + */ + private static readonly MAX_PROCESSED = 50_000 + getLastBlock(chain: string): number { return this.lastBlock[chain] ?? 0 } setLastBlock(chain: string, block: number): void { this.lastBlock[chain] = block } isProcessed(key: string): boolean { return this.processedSet.has(key) } - markProcessed(key: string): void { this.processedSet.add(key) } + + /** Settle a packet for good. Also drops any deferral, so the two never disagree. */ + markProcessed(key: string): void { + this.processedSet.add(key) + this.deferredMap.delete(key) + // Evict oldest-first (Set preserves insertion order). + while (this.processedSet.size > Checkpoint.MAX_PROCESSED) { + const oldest = this.processedSet.values().next().value + if (oldest === undefined) break + this.processedSet.delete(oldest) + } + } + + defer(key: string, record: DeferredRecord): void { this.deferredMap.set(key, record) } + getDeferred(key: string): DeferredRecord | undefined { return this.deferredMap.get(key) } + clearDeferred(key: string): void { this.deferredMap.delete(key) } + deferredEntries(): Array<[string, DeferredRecord]> { return [...this.deferredMap] } + + /** Held-packet counts per action, for the pending gauge. */ + deferredCounts(): Record { + const counts: Record = { delay: 0, 'manual-review': 0 } + for (const r of this.deferredMap.values()) counts[r.action]++ + return counts + } + + /** Record an owner approval seen on-chain. Persisted so it survives a restart. */ + addApproval(payloadHash: string): void { this.approvalSet.add(payloadHash.toLowerCase()) } + isApproved(payloadHash: string): boolean { return this.approvalSet.has(payloadHash.toLowerCase()) } save(): void { - const out: PersistedState = { lastBlock: this.lastBlock, processed: [...this.processedSet] } + const out: PersistedState = { + lastBlock: this.lastBlock, + processed: [...this.processedSet], + deferred: Object.fromEntries(this.deferredMap), + approvals: [...this.approvalSet], + feedVersions: this.feedVersions, + } const dir = dirname(this.path) if (dir && !existsSync(dir)) mkdirSync(dir, { recursive: true }) const tmp = `${this.path}.tmp` diff --git a/worker/cli.ts b/worker/cli.ts deleted file mode 100644 index 095cd65..0000000 --- a/worker/cli.ts +++ /dev/null @@ -1,209 +0,0 @@ -import 'dotenv/config' -import { ethers } from 'ethers' -import { Command, InvalidArgumentError } from 'commander' -import { loadConfig, CHAIN_REGISTRY, ResolvedChain } from './runtime/config' -import { buildDenylist, makeAssessor, combine, Assessment } from './assess/assess' -import { scanPacketSent } from './chain/events' -import { submitVerification, commitVerification } from './chain/verify' -import { trace } from './tracker/trace' - -const EVM_ADDRESS = /^0x[0-9a-fA-F]{40}$/ -const TX_HASH = /^0x[0-9a-fA-F]{64}$/ - -/** Exit codes: 0 success, 1 runtime error, 2 usage/validation error. */ -const EXIT = { OK: 0, RUNTIME: 1, USAGE: 2 } as const - -function parseAddress(value: string): string { - if (!EVM_ADDRESS.test(value)) throw new InvalidArgumentError('must be a 20-byte EVM address (0x + 40 hex chars)') - return value.toLowerCase() -} -function parseTxHash(value: string): string { - if (!TX_HASH.test(value)) throw new InvalidArgumentError('must be a 32-byte tx hash (0x + 64 hex chars)') - return value.toLowerCase() -} -function parseChainKey(value: string): string { - if (!(value in CHAIN_REGISTRY)) { - throw new InvalidArgumentError(`unknown chain '${value}' (known: ${Object.keys(CHAIN_REGISTRY).join(', ')})`) - } - return value -} - -/** Print a result as pretty JSON or human-readable text. */ -function emit(json: boolean, data: unknown, human: () => void): void { - if (json) process.stdout.write(JSON.stringify(data, null, 2) + '\n') - else human() -} - -function renderAssessment(label: string, a: Assessment): string { - const verdict = a.blocked ? 'BLOCKED' : 'clean' - const detail = a.blocked ? ` [${a.tags.join(', ')}] ${a.reasons.join('; ')}` : '' - return ` ${label}: ${a.address} -> ${verdict}${detail}` -} - -async function cmdAssess(address: string, opts: { json?: boolean }): Promise { - const assess = makeAssessor(await buildDenylist()) - const result = assess(address) - emit(!!opts.json, result, () => { - process.stdout.write(`Assessment for ${result.address}\n`) - process.stdout.write(` verdict: ${result.blocked ? 'BLOCKED' : 'clean'}\n`) - if (result.blocked) { - process.stdout.write(` tags: ${result.tags.join(', ')}\n`) - process.stdout.write(` reasons: ${result.reasons.join('; ')}\n`) - } - }) -} - -async function cmdTrace(txHash: string, opts: { json?: boolean }): Promise { - const result = await trace(txHash) - emit(!!opts.json, result, () => { - process.stdout.write(`Trace ${result.guid}\n`) - process.stdout.write(` pathway: eid ${result.srcEid} -> ${result.dstEid} status=${result.status}\n`) - process.stdout.write(renderAssessment('sender ', result.sender) + '\n') - process.stdout.write(renderAssessment('receiver', result.receiver) + '\n') - }) -} - -async function cmdVerify( - chainKey: string, - txHash: string, - opts: { json?: boolean; dryRun?: boolean }, -): Promise { - const config = loadConfig() - const src = config.chains.find((c) => c.key === chainKey) - if (!src) { - throw new CliError( - `chain '${chainKey}' is not enabled (enabled: ${config.chains.map((c) => c.key).join(', ')}); set CHAINS_ENABLED and the chain's DVN address`, - EXIT.USAGE, - ) - } - const byEid = new Map(config.chains.map((c) => [c.eid, c])) - - const provider = new ethers.providers.JsonRpcProvider(src.rpc) - const receipt = await provider.getTransactionReceipt(txHash) - if (!receipt) throw new CliError(`transaction ${txHash} not found on ${chainKey}`, EXIT.RUNTIME) - - const packets = await scanPacketSent(provider, src.endpoint, receipt.blockNumber, receipt.blockNumber) - const assess = makeAssessor(await buildDenylist()) - - const results: Array> = [] - for (const p of packets) { - const verdict = combine([assess(p.senderAddress), assess(p.receiverAddress), assess(p.oft.toAddress)]) - const dst = byEid.get(p.dstEid) - const row: Record = { - payloadHash: p.payloadHash, - dstEid: p.dstEid, - dstChain: dst?.key ?? null, - blocked: verdict.blocked, - tags: verdict.tags, - reasons: verdict.reasons, - action: 'pending', - } - - if (!dst) { - row.action = 'skipped:unknown-dst' - } else if (verdict.blocked) { - row.action = 'veto' - } else if (opts.dryRun) { - row.action = 'dry-run:would-verify' - } else { - const signer = new ethers.Wallet(config.privateKey, new ethers.providers.JsonRpcProvider(dst.rpc)) - const verifyTx = await submitVerification(signer, dst.dvn, p.header, p.payloadHash, config.confirmations) - row.verifyTx = verifyTx - try { - row.commitTx = await commitVerification(signer, dst.receiveUln, p.header, p.payloadHash) - row.action = 'verified+committed' - } catch (err) { - row.action = 'verified;commit-pending' - row.commitError = (err as Error).message - } - } - results.push(row) - } - - emit(!!opts.json, { chain: chainKey, txHash, packets: results }, () => { - process.stdout.write(`Verify ${chainKey} tx ${txHash} — ${results.length} packet(s)\n`) - for (const r of results) { - process.stdout.write(` ${r.payloadHash} -> dst=${r.dstChain ?? r.dstEid} action=${r.action}\n`) - if (r.blocked) process.stdout.write(` VETO [${(r.tags as string[]).join(', ')}] ${(r.reasons as string[]).join('; ')}\n`) - if (r.verifyTx) process.stdout.write(` verify tx: ${r.verifyTx}\n`) - if (r.commitTx) process.stdout.write(` commit tx: ${r.commitTx}\n`) - } - }) -} - -/** A CLI-layer error carrying the exit code to use. */ -class CliError extends Error { - constructor(message: string, readonly code: number) { - super(message) - } -} - -function buildProgram(): Command { - const program = new Command() - program - .name('dvn-cli') - .description('Compliance DVN operator CLI — screen addresses, verify packets, trace messages.') - .version('1.0.0') - .showHelpAfterError('(add --help for usage)') - - program - .command('assess') - .description('Assess one address against the sanctions denylist') - .argument('
', 'EVM address to screen', parseAddress) - .option('--json', 'emit machine-readable JSON') - .action(cmdAssess) - - program - .command('verify') - .description('Scan a transaction for LayerZero packets, screen parties, and verify or veto') - .argument('', `source chain (${Object.keys(CHAIN_REGISTRY).join(' | ')})`, parseChainKey) - .argument('', 'transaction hash on the source chain', parseTxHash) - .option('--json', 'emit machine-readable JSON') - .option('--dry-run', 'assess and report verdicts without sending transactions') - .action(cmdVerify) - - program - .command('trace') - .description('Trace a message via the LayerZero Scan API, risk-colored') - .argument('', 'source transaction hash', parseTxHash) - .option('--json', 'emit machine-readable JSON') - .action(cmdTrace) - - program.addHelpText( - 'after', - `\nExamples:\n $ dvn-cli assess 0x0000000000000000000000000000000000000000\n $ dvn-cli verify baseSepolia 0x --dry-run\n $ dvn-cli trace 0x --json\n`, - ) - - // exitOverride is per-command: apply it to the program AND every subcommand so all - // usage/validation errors surface to our handler for consistent exit codes. - program.exitOverride() - for (const c of program.commands) c.exitOverride() - - return program -} - -async function main(): Promise { - const program = buildProgram() - try { - await program.parseAsync(process.argv) - process.exit(EXIT.OK) - } catch (err) { - if (err instanceof CliError) { - process.stderr.write(`error: ${err.message}\n`) - process.exit(err.code) - } - // commander throws CommanderError for usage/help/version with its own exitCode. - const e = err as { code?: string; exitCode?: number; message?: string } - if (e?.code === 'commander.helpDisplayed' || e?.code === 'commander.version' || e?.code === 'commander.help') { - process.exit(EXIT.OK) - } - if (typeof e?.exitCode === 'number' && e.exitCode !== 0) { - // Validation / unknown-command / missing-argument errors from commander. - process.exit(EXIT.USAGE) - } - process.stderr.write(`error: ${e?.message ?? String(err)}\n`) - process.exit(EXIT.RUNTIME) - } -} - -void main() diff --git a/worker/deploy/grafana-dashboard.json b/worker/deploy/grafana-dashboard.json index d61f26d..1fde111 100644 --- a/worker/deploy/grafana-dashboard.json +++ b/worker/deploy/grafana-dashboard.json @@ -1,19 +1,28 @@ { "title": "Compliance DVN Worker", "uid": "compliance-dvn-worker", - "tags": ["compliance-dvn", "layerzero"], + "tags": [ + "compliance-dvn", + "layerzero" + ], "schemaVersion": 39, "version": 1, "timezone": "", "refresh": "30s", - "time": { "from": "now-6h", "to": "now" }, + "time": { + "from": "now-6h", + "to": "now" + }, "templating": { "list": [ { "name": "datasource", "type": "datasource", "query": "prometheus", - "current": {}, + "current": { + "text": "DVN Prometheus", + "value": "dvn-prometheus" + }, "hide": 0 } ] @@ -23,115 +32,400 @@ "id": 1, "title": "Status", "type": "stat", - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "gridPos": { "h": 4, "w": 6, "x": 0, "y": 0 }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 0 + }, "fieldConfig": { "defaults": { "mappings": [ - { "type": "value", "options": { "0": { "text": "NOT READY", "color": "red" }, "1": { "text": "READY", "color": "green" } } } + { + "type": "value", + "options": { + "0": { + "text": "NOT READY", + "color": "red" + }, + "1": { + "text": "READY", + "color": "green" + } + } + } ], - "color": { "mode": "thresholds" }, - "thresholds": { "steps": [ { "color": "red", "value": null }, { "color": "green", "value": 1 } ] } + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 1 + } + ] + } } }, - "targets": [ { "expr": "max(dvn_ready)", "refId": "A" } ] + "targets": [ + { + "expr": "max(dvn_ready)", + "refId": "A" + } + ] }, { "id": 2, "title": "HALTED (fail-closed)", "type": "stat", - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "gridPos": { "h": 4, "w": 6, "x": 6, "y": 0 }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 0 + }, "fieldConfig": { "defaults": { "mappings": [ - { "type": "value", "options": { "0": { "text": "no", "color": "green" }, "1": { "text": "HALTED", "color": "red" } } } + { + "type": "value", + "options": { + "0": { + "text": "no", + "color": "green" + }, + "1": { + "text": "HALTED", + "color": "red" + } + } + } ], - "color": { "mode": "thresholds" }, - "thresholds": { "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 1 } ] } + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 1 + } + ] + } } }, - "targets": [ { "expr": "max(dvn_halted)", "refId": "A" } ] + "targets": [ + { + "expr": "max(dvn_halted)", + "refId": "A" + } + ] }, { "id": 3, "title": "Denylist age (s)", "type": "stat", - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "gridPos": { "h": 4, "w": 6, "x": 12, "y": 0 }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 12, + "y": 0 + }, "fieldConfig": { "defaults": { "unit": "s", - "color": { "mode": "thresholds" }, - "thresholds": { "steps": [ { "color": "green", "value": null }, { "color": "yellow", "value": 3000 }, { "color": "red", "value": 3600 } ] } + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 3000 + }, + { + "color": "red", + "value": 3600 + } + ] + } } }, - "targets": [ { "expr": "max(dvn_denylist_age_seconds)", "refId": "A" } ] + "targets": [ + { + "expr": "max(dvn_denylist_age_seconds)", + "refId": "A" + } + ] }, { "id": 4, "title": "Denylist size by source", "type": "piechart", - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "gridPos": { "h": 4, "w": 6, "x": 18, "y": 0 }, - "targets": [ { "expr": "dvn_denylist_size", "legendFormat": "{{source}}", "refId": "A" } ] + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 0 + }, + "targets": [ + { + "expr": "dvn_denylist_size", + "legendFormat": "{{source}}", + "refId": "A" + } + ] }, { "id": 5, - "title": "Vetoes (sanctioned transfers withheld)", + "title": "Risk decisions by action", + "type": "timeseries", + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 4 + }, + "targets": [ + { + "expr": "sum by (chain, action) (rate(dvn_decisions_total[5m]))", + "legendFormat": "{{chain}} / {{action}}", + "refId": "A" + } + ] + }, + { + "id": 11, + "title": "Held packets (awaiting delay / approval)", "type": "timeseries", - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "gridPos": { "h": 8, "w": 12, "x": 0, "y": 4 }, - "targets": [ { "expr": "sum by (chain, tag) (rate(dvn_vetoes_total[5m]))", "legendFormat": "{{chain}} / {{tag}}", "refId": "A" } ] + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 20 + }, + "targets": [ + { + "expr": "dvn_pending_packets", + "legendFormat": "{{action}}", + "refId": "A" + }, + { + "expr": "sum by (chain) (rate(dvn_approvals_total[5m]))", + "legendFormat": "approvals {{chain}}", + "refId": "B" + } + ] }, { "id": 6, "title": "Verifications & commits (rate)", "type": "timeseries", - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "gridPos": { "h": 8, "w": 12, "x": 12, "y": 4 }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 4 + }, "targets": [ - { "expr": "sum by (result) (rate(dvn_verifications_total[5m]))", "legendFormat": "verify {{result}}", "refId": "A" }, - { "expr": "sum by (result) (rate(dvn_commits_total[5m]))", "legendFormat": "commit {{result}}", "refId": "B" } + { + "expr": "sum by (result) (rate(dvn_verifications_total[5m]))", + "legendFormat": "verify {{result}}", + "refId": "A" + }, + { + "expr": "sum by (result) (rate(dvn_commits_total[5m]))", + "legendFormat": "commit {{result}}", + "refId": "B" + } ] }, { "id": 7, "title": "Scan lag (head - checkpoint)", "type": "timeseries", - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "gridPos": { "h": 8, "w": 12, "x": 0, "y": 12 }, - "targets": [ { "expr": "dvn_chain_head_block - dvn_checkpoint_block", "legendFormat": "{{chain}}", "refId": "A" } ] + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 12 + }, + "targets": [ + { + "expr": "dvn_chain_head_block - dvn_checkpoint_block", + "legendFormat": "{{chain}}", + "refId": "A" + } + ] }, { "id": 8, "title": "Tx send latency p95 (s)", "type": "timeseries", - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "gridPos": { "h": 8, "w": 12, "x": 12, "y": 12 }, - "fieldConfig": { "defaults": { "unit": "s" } }, - "targets": [ { "expr": "histogram_quantile(0.95, sum by (le, chain, op) (rate(dvn_tx_send_seconds_bucket[5m])))", "legendFormat": "{{chain}} {{op}}", "refId": "A" } ] + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 12 + }, + "fieldConfig": { + "defaults": { + "unit": "s" + } + }, + "targets": [ + { + "expr": "histogram_quantile(0.95, sum by (le, chain, op) (rate(dvn_tx_send_seconds_bucket[5m])))", + "legendFormat": "{{chain}} {{op}}", + "refId": "A" + } + ] }, { "id": 9, "title": "Packets scanned / assigned (rate)", "type": "timeseries", - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "gridPos": { "h": 8, "w": 12, "x": 0, "y": 20 }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 20 + }, "targets": [ - { "expr": "sum by (chain) (rate(dvn_packets_scanned_total[5m]))", "legendFormat": "scanned {{chain}}", "refId": "A" }, - { "expr": "sum by (chain) (rate(dvn_packets_assigned_total[5m]))", "legendFormat": "assigned {{chain}}", "refId": "B" } + { + "expr": "sum by (chain) (rate(dvn_packets_scanned_total[5m]))", + "legendFormat": "scanned {{chain}}", + "refId": "A" + }, + { + "expr": "sum by (chain) (rate(dvn_packets_assigned_total[5m]))", + "legendFormat": "assigned {{chain}}", + "refId": "B" + } ] }, { "id": 10, "title": "Scan errors & refresh failures (rate)", "type": "timeseries", - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "gridPos": { "h": 8, "w": 12, "x": 12, "y": 20 }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 20 + }, + "targets": [ + { + "expr": "sum by (chain) (rate(dvn_scan_errors_total[5m]))", + "legendFormat": "scan err {{chain}}", + "refId": "A" + }, + { + "expr": "sum by (result) (rate(dvn_denylist_refresh_total[15m]))", + "legendFormat": "refresh {{result}}", + "refId": "B" + } + ] + }, + { + "id": 12, + "title": "Risk evidence observed (by signal type)", + "type": "timeseries", + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 28 + }, "targets": [ - { "expr": "sum by (chain) (rate(dvn_scan_errors_total[5m]))", "legendFormat": "scan err {{chain}}", "refId": "A" }, - { "expr": "sum by (result) (rate(dvn_denylist_refresh_total[15m]))", "legendFormat": "refresh {{result}}", "refId": "B" } + { + "expr": "sum by (type, source) (increase(dvn_screening_evidence_total[15m]))", + "legendFormat": "{{type}} ({{source}})", + "refId": "A" + } + ] + }, + { + "id": 13, + "title": "Evidence totals by type", + "type": "piechart", + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 28 + }, + "targets": [ + { + "expr": "sum by (type) (dvn_screening_evidence_total)", + "legendFormat": "{{type}}", + "refId": "A" + } ] } ] diff --git a/worker/deploy/k8s/configmap.yaml b/worker/deploy/k8s/configmap.yaml index aaca9a1..4e0f5ec 100644 --- a/worker/deploy/k8s/configmap.yaml +++ b/worker/deploy/k8s/configmap.yaml @@ -5,7 +5,7 @@ metadata: labels: app.kubernetes.io/name: compliance-dvn-worker data: - # Non-secret tuning. Secrets (PRIVATE_KEY, DVN addresses) come from the Secret. + # Non-secret tuning. Secrets (OPERATOR_PRIVATE_KEY, DVN addresses) come from the Secret. NODE_ENV: "production" LOG_LEVEL: "info" HTTP_PORT: "9090" diff --git a/worker/deploy/k8s/prometheusrule.yaml b/worker/deploy/k8s/prometheusrule.yaml index e35085d..6af0d7e 100644 --- a/worker/deploy/k8s/prometheusrule.yaml +++ b/worker/deploy/k8s/prometheusrule.yaml @@ -58,3 +58,27 @@ spec: annotations: summary: "Chain scan errors" description: "Repeated scan/RPC errors on {{ $labels.chain }}." + - alert: DvnSourceDegraded + expr: max by (source) (dvn_source_degraded) > 0 + for: 15m + labels: + severity: warning + annotations: + summary: "Risk source unavailable — screening is degraded" + description: "{{ $labels.source }} has been unavailable for 15m. Verification continues on authoritative sources alone, so graph-derived labels (N-hop exposure, mixer proximity) are NOT being applied. Check dvn_feed_rejected_total for the reason." + - alert: DvnFeedRejected + expr: increase(dvn_feed_rejected_total[15m]) > 2 + for: 0m + labels: + severity: warning + annotations: + summary: "Indexer feeds are being rejected" + description: "More than 2 feed rejections in 15m with reason '{{ $labels.reason }}'. untrusted_signer or bad_signature may indicate a misconfigured allowlist or a tampered feed; policy_mismatch means the indexer and worker are on different policy versions." + - alert: DvnPacketsAwaitingReview + expr: max(dvn_pending_packets{action="manual-review"}) > 0 + for: 30m + labels: + severity: warning + annotations: + summary: "Packets awaiting owner approval" + description: "{{ $value }} packet(s) have been held for manual review for 30m. Nothing but an on-chain approvePacket call releases them — run `dvn-cli pending` and decide." diff --git a/worker/deploy/k8s/secret.example.yaml b/worker/deploy/k8s/secret.example.yaml index ff8e509..dbac3fd 100644 --- a/worker/deploy/k8s/secret.example.yaml +++ b/worker/deploy/k8s/secret.example.yaml @@ -2,7 +2,7 @@ # in production. Create the real Secret out-of-band: # # kubectl create secret generic compliance-dvn-worker-secrets \ -# --from-literal=PRIVATE_KEY=0x... \ +# --from-literal=OPERATOR_PRIVATE_KEY=0x... \ # --from-literal=DVN_BASE_SEPOLIA=0x... \ # --from-literal=DVN_OPTIMISM_SEPOLIA=0x... # @@ -14,6 +14,7 @@ metadata: app.kubernetes.io/name: compliance-dvn-worker type: Opaque stringData: - PRIVATE_KEY: "0x0000000000000000000000000000000000000000000000000000000000000000" + # The OPERATOR key. The OWNER key (which approves held packets) must never be mounted here. + OPERATOR_PRIVATE_KEY: "0x0000000000000000000000000000000000000000000000000000000000000000" DVN_BASE_SEPOLIA: "0x0000000000000000000000000000000000000000" DVN_OPTIMISM_SEPOLIA: "0x0000000000000000000000000000000000000000" diff --git a/worker/package.json b/worker/package.json index 9514bb6..cf6d452 100644 --- a/worker/package.json +++ b/worker/package.json @@ -2,7 +2,7 @@ "name": "compliance-dvn-worker", "version": "1.0.0", "private": true, - "description": "LayerZero Compliance DVN — sanctions-screening verifier worker + operator CLI", + "description": "LayerZero Compliance DVN — sanctions-screening verifier worker", "license": "MIT", "packageManager": "pnpm@9.15.9", "engines": { @@ -10,13 +10,11 @@ }, "scripts": { "start": "tsx service.ts", - "cli": "tsx cli.ts", "test": "vitest run test", "test:watch": "vitest test", "typecheck": "tsc --noEmit -p tsconfig.json" }, "dependencies": { - "commander": "^15.0.0", "dotenv": "^17.4.2", "ethers": "^5.7.2", "node-fetch": "^3.3.2", diff --git a/worker/pnpm-lock.yaml b/worker/pnpm-lock.yaml index fcd91e8..466bb78 100644 --- a/worker/pnpm-lock.yaml +++ b/worker/pnpm-lock.yaml @@ -8,9 +8,6 @@ importers: .: dependencies: - commander: - specifier: ^15.0.0 - version: 15.0.0 dotenv: specifier: ^17.4.2 version: 17.4.2 @@ -511,10 +508,6 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} - commander@15.0.0: - resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==} - engines: {node: '>=22.12.0'} - convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -1528,8 +1521,6 @@ snapshots: dependencies: delayed-stream: 1.0.0 - commander@15.0.0: {} - convert-source-map@2.0.0: {} data-uri-to-buffer@4.0.1: {} diff --git a/worker/runtime/actions.ts b/worker/runtime/actions.ts index 27ab7f3..716f3ae 100644 --- a/worker/runtime/actions.ts +++ b/worker/runtime/actions.ts @@ -2,19 +2,32 @@ import { ethers } from 'ethers' import type { ResolvedChain } from './config' import type { TxSender } from './tx-sender' import type { VerifyPacketDeps } from './scanner' +import type { OnChainVerdict } from '../assess/verdict' +import { decodeHeader } from '../chain/header' const DVN_ABI = [ - 'function submitVerification(bytes packetHeader, bytes32 payloadHash, uint64 confirmations) external', + 'function submitVerification(bytes packetHeader, bytes32 payloadHash, uint64 confirmations, uint8 action, uint16 score, uint256 reasonMask, bytes32 evidenceHash) external', + 'function recordVerdict(bytes32 payloadHash, uint8 action, uint16 score, uint256 reasonMask, bytes32 evidenceHash) external', ] const RECEIVE_ULN_ABI = ['function commitVerification(bytes packetHeader, bytes32 payloadHash) external'] +const ENDPOINT_ABI = [ + 'function lzReceive((uint32 srcEid, bytes32 sender, uint64 nonce) origin, address receiver, bytes32 guid, bytes message, bytes extraData) external payable', + 'function lazyInboundNonce(address receiver, uint32 srcEid, bytes32 sender) view returns (uint64)', + 'function inboundPayloadHash(address receiver, uint32 srcEid, bytes32 sender, uint64 nonce) view returns (bytes32)', +] + +const HASH_ZERO = '0x' + '0'.repeat(64) -export type Actions = Pick +export type Actions = Pick< + VerifyPacketDeps, + 'verify' | 'commit' | 'recordVerdict' | 'execute' | 'abandoned' | 'commitState' +> /** - * Wire the on-chain verify/commit calls through each destination chain's TxSender, so every - * transaction gets sequential nonces, gas escalation, and bounded retries. The DVN's - * `submitVerification` and the ReceiveUln's `commitVerification` both run on the destination - * chain, keyed by `dst.key`. + * Wire the on-chain calls through each destination chain's TxSender, so every transaction gets + * sequential nonces, gas escalation, and bounded retries. `submitVerification` and + * `recordVerdict` are on our DVN; the ReceiveUln's `commitVerification` is not — all three run + * on the destination chain, keyed by `dst.key`. */ export function createActions( signers: Record, @@ -22,12 +35,22 @@ export function createActions( confirmations: number, ): Actions { const toBn = (gasPrice: bigint) => ethers.BigNumber.from(gasPrice.toString()) + const mask = (v: bigint) => ethers.BigNumber.from(v.toString()) return { - verify: (dst: ResolvedChain, header: string, payloadHash: string) => { + verify: (dst: ResolvedChain, header: string, payloadHash: string, verdict: OnChainVerdict) => { const dvn = new ethers.Contract(dst.dvn, DVN_ABI, signers[dst.key]) return senders[dst.key].send('verify', ({ nonce, gasPrice }) => - dvn.submitVerification(header, payloadHash, confirmations, { nonce, gasPrice: toBn(gasPrice) }), + dvn.submitVerification( + header, + payloadHash, + confirmations, + verdict.action, + verdict.score, + mask(verdict.reasonMask), + verdict.evidenceHash, + { nonce, gasPrice: toBn(gasPrice) }, + ), ) }, commit: (dst: ResolvedChain, header: string, payloadHash: string) => { @@ -36,5 +59,83 @@ export function createActions( uln.commitVerification(header, payloadHash, { nonce, gasPrice: toBn(gasPrice) }), ) }, + /** + * Run `lzReceive` on the destination endpoint, delivering the message. + * + * Committing only makes a packet executable; the delivery itself is a separate call that the + * LayerZero executor normally makes. It does not track custom DVNs, so a committed packet can + * sit undelivered indefinitely — the same reason the worker already drives the commit. Execution + * is permissionless once committed, so the operator key suffices. + * + * The origin is decoded from the packet header rather than passed alongside it, so a packet + * released from the deferred queue needs nothing beyond what it already persisted. + */ + execute: (dst: ResolvedChain, header: string, guid: string, message: string) => { + const endpoint = new ethers.Contract(dst.endpoint, ENDPOINT_ABI, signers[dst.key]) + const h = decodeHeader(header) + const origin = { srcEid: h.srcEid, sender: h.sender, nonce: h.nonce.toString() } + return senders[dst.key].send('lzReceive', ({ nonce, gasPrice }) => + endpoint.lzReceive(origin, h.receiverAddress, guid, message, '0x', { + nonce, + gasPrice: toBn(gasPrice), + }), + ) + }, + /** + * Has the owner abandoned this packet by skipping its nonce? + * + * `EndpointV2.skip` is the only way to give up on a message: it moves `lazyInboundNonce` past + * the slot without ever setting a payload hash, so the channel can move on and that nonce can + * never execute again. A read, not a write — the worker cannot skip anything itself (skipping + * is the OApp delegate's call, not the operator's), it only notices that a human did. + * + * The pair of reads is what distinguishes a skip from a delivery: both leave the nonce behind + * `lazyInboundNonce`, but a delivered packet had a payload hash committed first. A packet in + * the deferred queue was never verified by us, so it cannot have been delivered. + */ + abandoned: async (dst: ResolvedChain, header: string) => { + const endpoint = new ethers.Contract(dst.endpoint, ENDPOINT_ABI, signers[dst.key]) + const h = decodeHeader(header) + const lazy: ethers.BigNumber = await endpoint.lazyInboundNonce(h.receiverAddress, h.srcEid, h.sender) + if (lazy.lt(h.nonce.toString())) return false + const committed: string = await endpoint.inboundPayloadHash( + h.receiverAddress, + h.srcEid, + h.sender, + h.nonce.toString(), + ) + return committed === HASH_ZERO + }, + /** + * How far the packet already is on the destination — the question a failed commit or delivery + * actually raises. Read from the endpoint rather than guessed from a revert selector: the ULN + * reports "already committed" and "not verified yet" identically, because committing deletes + * the attestation it would otherwise have found. + */ + commitState: async (dst: ResolvedChain, header: string, payloadHash: string) => { + const endpoint = new ethers.Contract(dst.endpoint, ENDPOINT_ABI, signers[dst.key]) + const h = decodeHeader(header) + const nonce = h.nonce.toString() + const committed: string = await endpoint.inboundPayloadHash(h.receiverAddress, h.srcEid, h.sender, nonce) + if (committed.toLowerCase() === payloadHash.toLowerCase()) return 'committed' + // A slot holding some other payload is not ours to commit and not a race we lost either; + // reported as pending so it surfaces rather than being quietly accepted. + if (committed !== HASH_ZERO) return 'pending' + const lazy: ethers.BigNumber = await endpoint.lazyInboundNonce(h.receiverAddress, h.srcEid, h.sender) + return lazy.lt(nonce) ? 'pending' : 'cleared' + }, + recordVerdict: (dst: ResolvedChain, payloadHash: string, verdict: OnChainVerdict) => { + const dvn = new ethers.Contract(dst.dvn, DVN_ABI, signers[dst.key]) + return senders[dst.key].send('recordVerdict', ({ nonce, gasPrice }) => + dvn.recordVerdict( + payloadHash, + verdict.action, + verdict.score, + mask(verdict.reasonMask), + verdict.evidenceHash, + { nonce, gasPrice: toBn(gasPrice) }, + ), + ) + }, } } diff --git a/worker/runtime/config.ts b/worker/runtime/config.ts index 7b7fd16..36a77bf 100644 --- a/worker/runtime/config.ts +++ b/worker/runtime/config.ts @@ -57,11 +57,26 @@ export interface ResolvedChain { export interface Config { readonly nodeEnv: string - readonly privateKey: string + /** The operator key the worker signs verify/commit/recordVerdict with. */ + readonly operatorPrivateKey: string readonly chains: readonly ResolvedChain[] readonly pollMs: number + /** + * Confirmations asserted in `submitVerification`. Must be at least the pathway's ULN + * `confirmations`, or the destination does not treat the packet as verifiable. + */ readonly confirmations: number + /** + * How far behind the head to stop scanning. Independent of the attested value above: reading a + * block sooner is a latency choice, while the attested number is a protocol requirement. + */ + readonly scanConfirmations: number readonly denylistRefreshMs: number + /** + * How often to re-ingest the indexer feed alone. Much shorter than a full rebuild because it is + * one local request, not a re-download of OFAC and OpenSanctions. + */ + readonly feedRefreshMs: number readonly maxDenylistStalenessMs: number readonly txMaxRetries: number readonly txGasBumpPct: number @@ -69,9 +84,29 @@ export interface Config { readonly logLevel: string readonly checkpointPath: string readonly testDenylist: string + /** Empty disables indexer feed ingest entirely. */ + readonly indexerFeedUrl: string + readonly indexerSigners: readonly string[] + readonly feedMaxSkewSec: number + readonly degradedMode: 'degrade' | 'halt' + /** + * Non-allow actions that get a separate `recordVerdict` transaction. `allow` is never listed: + * it rides along on `submitVerification` at no extra cost and is always recorded. + */ + readonly emitVerdictFor: readonly ('delay' | 'manual-review' | 'block')[] } -const HEX_PRIVATE_KEY = /^0x[0-9a-fA-F]{64}$/ +/** + * A 32-byte private key, with the 0x prefix optional. + * + * ethers accepts a bare 64-hex key, so requiring the prefix would reject a configuration that + * works perfectly well. Values are normalized to the 0x form below so everything downstream sees + * one shape. + */ +const HEX_PRIVATE_KEY = /^(0x)?[0-9a-fA-F]{64}$/ + +/** Canonicalize to the 0x form. */ +const withHexPrefix = (v: string) => (v.startsWith('0x') ? v : `0x${v}`) const EVM_ADDRESS = /^0x[0-9a-fA-F]{40}$/ const LOG_LEVELS = ['fatal', 'error', 'warn', 'info', 'debug', 'trace', 'silent'] as const @@ -85,12 +120,27 @@ function intField(def: number, min: number, max = Number.MAX_SAFE_INTEGER) { const ScalarSchema = z.object({ NODE_ENV: z.string().optional().default('production'), - PRIVATE_KEY: z - .string({ error: 'PRIVATE_KEY is required' }) - .regex(HEX_PRIVATE_KEY, 'PRIVATE_KEY must be a 32-byte hex string (0x + 64 hex chars)'), + /** + * The OPERATOR key, named explicitly rather than as a bare `PRIVATE_KEY`. + * + * The repo root's .env has a `PRIVATE_KEY` too, and there it is the OWNER key. Sharing the name + * across the two files made one mistake — copying root .env into worker/ — silently hand the + * worker the owner key, and with it the ability to approve the very packets it withheld. The + * distinct name means that copy fails loudly instead. + */ + OPERATOR_PRIVATE_KEY: z + .string({ error: 'OPERATOR_PRIVATE_KEY is required' }) + .regex(HEX_PRIVATE_KEY, 'OPERATOR_PRIVATE_KEY must be a 32-byte hex key (64 hex chars, 0x prefix optional)') + .transform(withHexPrefix), POLL_MS: intField(15_000, 1), DVN_CONFIRMATIONS: intField(5, 0), + /** Defaults to DVN_CONFIRMATIONS below when unset, preserving the previous single-knob behaviour. */ + SCAN_CONFIRMATIONS: z.preprocess( + (v) => (v === undefined || v === '' ? undefined : v), + z.coerce.number().int().min(0).optional(), + ), DENYLIST_REFRESH_MS: intField(1_800_000, 1), + FEED_REFRESH_MS: intField(30_000, 1), MAX_DENYLIST_STALENESS_MS: intField(3_600_000, 1), TX_MAX_RETRIES: intField(3, 0, 20), TX_GAS_BUMP_PCT: intField(15, 0, 1000), @@ -108,6 +158,30 @@ const ScalarSchema = z.object({ .string() .optional() .transform((v) => v ?? ''), + SCAM_TOKENS: z + .string() + .optional() + .transform((v) => v ?? ''), + INDEXER_FEED_URL: z + .string() + .optional() + .transform((v) => (v ?? '').trim()), + INDEXER_SIGNERS: z + .string() + .optional() + .transform((v) => (v ?? '').trim()), + FEED_MAX_SKEW_SEC: intField(300, 0, 86_400), + DEGRADED_MODE: z + .string() + .optional() + .transform((v) => (v === undefined || v === '' ? 'degrade' : v)) + .pipe(z.enum(['degrade', 'halt'])), + EMIT_VERDICT_EVENTS: z + .string() + .optional() + .transform((v) => (v === undefined ? 'block' : v.trim())) + .transform((v) => (v === '' ? [] : v.split(',').map((s) => s.trim()).filter(Boolean))) + .pipe(z.array(z.enum(['delay', 'manual-review', 'block']))), }) /** Thrown when the environment fails validation; `.message` lists every problem. */ @@ -118,13 +192,40 @@ export class ConfigError extends Error { } } +export interface LoadConfigOptions { + /** + * Refuse to boot when an owner-capable key is present in the environment, even alongside a + * valid OPERATOR_PRIVATE_KEY. The long-running service sets this: the owner key approves the + * very packets the worker withholds, so the two must never share a process environment. Owner + * actions (approve, skip) are signed elsewhere — in MetaMask via the dashboard, or a deploy + * shell — never by this process. + */ + forbidOwnerKeys?: boolean +} + +/** Env vars that carry owner authority; see the deploy scripts. */ +const OWNER_KEY_VARS = ['PRIVATE_KEY', 'OWNER_PRIVATE_KEY'] as const + /** * Validate the environment and resolve the active chain set. Fails fast with a single * aggregated error enumerating every problem, so an operator fixes one boot, not ten. */ -export function loadConfig(env: NodeJS.ProcessEnv = process.env): Config { +export function loadConfig(env: NodeJS.ProcessEnv = process.env, opts: LoadConfigOptions = {}): Config { const problems: string[] = [] + if (opts.forbidOwnerKeys) { + for (const key of OWNER_KEY_VARS) { + if ((env[key] ?? '').trim()) { + problems.push( + `${key} must not be set in the worker service's environment. It is an OWNER key — the one ` + + 'that approves held packets — and a worker holding it could release its own holds. Keep it ' + + 'in your deploy shell (owner actions are signed in MetaMask via the dashboard) and give ' + + 'the service only OPERATOR_PRIVATE_KEY.', + ) + } + } + } + const scalar = ScalarSchema.safeParse(env) if (!scalar.success) { for (const issue of scalar.error.issues) { @@ -133,6 +234,18 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): Config { } } + // Name the likely cause rather than just the symptom. A bare PRIVATE_KEY with no + // OPERATOR_PRIVATE_KEY almost always means the repo root's .env was copied here — and there + // PRIVATE_KEY is the OWNER key. Running with it would give the worker approval rights over the + // packets it withheld, so this is worth spelling out instead of a generic "required" message. + if (!(env.OPERATOR_PRIVATE_KEY ?? '').trim() && (env.PRIVATE_KEY ?? '').trim()) { + problems.push( + 'PRIVATE_KEY is set but OPERATOR_PRIVATE_KEY is not. The worker signs with the OPERATOR key; ' + + "the repo root's PRIVATE_KEY is the OWNER key and must never reach this process — it is what " + + 'approves held packets. Set OPERATOR_PRIVATE_KEY to the worker key and remove PRIVATE_KEY.', + ) + } + // Resolve the enabled chain set independently of scalar success so we surface all problems. const known = Object.keys(CHAIN_REGISTRY) const enabledRaw = (env.CHAINS_ENABLED ?? '').trim() @@ -177,16 +290,35 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): Config { ) } + // An unsigned feed is worse than no feed: without an allowlist any host that answers the URL + // could inject labels, so refuse to start rather than ingest one unverified. + // + // Read from the raw environment rather than the parsed result, so a bad allowlist is reported + // alongside any other problem instead of only after that one is fixed. + const indexerSigners = (env.INDEXER_SIGNERS ?? '') + .split(',') + .map((s) => s.trim().toLowerCase()) + .filter(Boolean) + if ((env.INDEXER_FEED_URL ?? '').trim() !== '') { + if (indexerSigners.length === 0) { + problems.push('INDEXER_SIGNERS: required when INDEXER_FEED_URL is set (comma-separated EVM addresses)') + } + const bad = indexerSigners.filter((s) => !EVM_ADDRESS.test(s)) + if (bad.length) problems.push(`INDEXER_SIGNERS: not valid EVM addresses: ${bad.join(', ')}`) + } + if (problems.length) throw new ConfigError(problems) const d = scalar.data! return Object.freeze({ nodeEnv: d.NODE_ENV, - privateKey: d.PRIVATE_KEY, + operatorPrivateKey: d.OPERATOR_PRIVATE_KEY, chains: Object.freeze(chains), pollMs: d.POLL_MS, confirmations: d.DVN_CONFIRMATIONS, + scanConfirmations: d.SCAN_CONFIRMATIONS ?? d.DVN_CONFIRMATIONS, denylistRefreshMs: d.DENYLIST_REFRESH_MS, + feedRefreshMs: d.FEED_REFRESH_MS, maxDenylistStalenessMs: d.MAX_DENYLIST_STALENESS_MS, txMaxRetries: d.TX_MAX_RETRIES, txGasBumpPct: d.TX_GAS_BUMP_PCT, @@ -194,5 +326,10 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): Config { logLevel: d.LOG_LEVEL, checkpointPath: d.CHECKPOINT_PATH, testDenylist: d.TEST_DENYLIST, + indexerFeedUrl: d.INDEXER_FEED_URL, + indexerSigners: Object.freeze(indexerSigners), + feedMaxSkewSec: d.FEED_MAX_SKEW_SEC, + degradedMode: d.DEGRADED_MODE, + emitVerdictFor: Object.freeze(d.EMIT_VERDICT_EVENTS), }) } diff --git a/worker/runtime/denylist-manager.ts b/worker/runtime/denylist-manager.ts index c492892..28085d7 100644 --- a/worker/runtime/denylist-manager.ts +++ b/worker/runtime/denylist-manager.ts @@ -1,19 +1,36 @@ import type { Logger } from 'pino' -import { Denylist } from '../assess/store' -import { makeAssessor, Assessor, buildDenylist } from '../assess/assess' +import { RiskStore } from '../assess/store' +import { makeAssessor, Assessor, buildRiskStore, type AssessorProviders, type RiskStoreBuild } from '../assess/assess' import type { Metrics } from './metrics' export type DvnState = 'INITIALIZING' | 'READY' | 'REFRESHING' | 'HALTED' +/** What to do when a tolerated source (the indexer feed) is unavailable. */ +export type DegradedMode = 'degrade' | 'halt' + export interface DenylistManagerDeps { - /** Builds a fresh denylist from all sources. Injectable for tests. */ - build?: () => Promise + /** Builds a fresh risk store from all sources. Injectable for tests. */ + build?: () => Promise + /** Live chain checks layered over the store. Omit for store-only screening. */ + providers?: AssessorProviders /** Monotonic-enough clock in ms. Injectable for tests. */ now?: () => number /** Sleep used between initial-build retries. Injectable for tests. */ sleep?: (ms: number) => Promise + /** + * Re-ingest the indexer feed into the current store. Optional; without it feed labels only + * arrive with a full rebuild. Returns the number of entries applied. + */ + refreshFeed?: (store: RiskStore) => Promise refreshMs: number + /** How often to run `refreshFeed`. Omit (or match `refreshMs`) to leave feed timing unchanged. */ + feedRefreshMs?: number maxStalenessMs: number + /** + * `degrade` keeps verifying on authoritative sources alone when the feed is missing; + * `halt` withholds everything until the feed returns. Defaults to `degrade`. + */ + degradedMode?: DegradedMode logger: Logger metrics: Metrics initialBackoffMs?: number @@ -21,34 +38,47 @@ export interface DenylistManagerDeps { } /** - * Owns the current denylist and the fail-closed lifecycle around it. + * Owns the current risk store and the fail-closed lifecycle around it. + * + * The store is built once at start (retried with backoff until the first success — we never + * enter READY without a valid store) and refreshed on an interval. Two gates decide readiness: * - * The list is built once at start (retried with backoff until the first success — we never - * enter READY without a valid list) and refreshed on an interval. Staleness is the single - * gate: any time the list ages past `maxStalenessMs`, the manager reports HALTED so the - * service withholds all verification until a refresh succeeds. + * - **Staleness.** Any time the store ages past `maxStalenessMs`, the manager reports HALTED so + * the service withholds all verification until a refresh succeeds. + * - **Degradation.** A build that lost a tolerated source still produces a usable store. Under + * `degradedMode: 'halt'` that is enough to withhold verification; under `degrade` (the + * default) screening continues on authoritative sources alone, loudly. */ export class DenylistManager { - private current: Denylist | undefined + private current: RiskStore | undefined private builtAt = 0 private _state: DvnState = 'INITIALIZING' + private _degraded: string[] = [] private timer: NodeJS.Timeout | undefined + private feedTimer: NodeJS.Timeout | undefined private stopped = false - private readonly build: () => Promise + private readonly build: () => Promise private readonly now: () => number private readonly sleep: (ms: number) => Promise + private readonly degradedMode: DegradedMode private readonly initialBackoffMs: number private readonly maxBackoffMs: number constructor(private readonly deps: DenylistManagerDeps) { - this.build = deps.build ?? buildDenylist + this.build = deps.build ?? (() => buildRiskStore()) this.now = deps.now ?? Date.now this.sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms))) + this.degradedMode = deps.degradedMode ?? 'degrade' this.initialBackoffMs = deps.initialBackoffMs ?? 1000 this.maxBackoffMs = deps.maxBackoffMs ?? 60_000 } + /** Sources that failed on the last build but were tolerated. */ + get degraded(): readonly string[] { + return this._degraded + } + get state(): DvnState { return this._state } @@ -62,13 +92,13 @@ export class DenylistManager { } /** - * Current assessor over the live denylist. Throws unless READY — defence-in-depth so a - * caller can never screen against a list the state machine considers unsafe (stale/HALTED). + * Current assessor over the live risk store. Throws unless READY — defence-in-depth so a + * caller can never screen against a store the state machine considers unsafe (stale/HALTED). */ assessor(): Assessor { if (!this.current) throw new Error('DenylistManager not ready: no denylist built yet') if (this._state !== 'READY') throw new Error(`DenylistManager not ready: state is ${this._state}`) - return makeAssessor(this.current) + return makeAssessor(this.current, this.deps.providers) } /** Build the initial list (retrying until success), then schedule periodic refreshes. */ @@ -77,6 +107,9 @@ export class DenylistManager { for (let attempt = 1; !this.stopped; attempt++) { try { await this.adopt(await this.build()) + // Counted as a refresh because the failure path below already is: leaving the success out + // would make the initial build look like pure failures until the first periodic refresh. + this.deps.metrics.denylistRefreshTotal.inc({ result: 'success' }) this.deps.logger.info({ size: this.current!.size, attempt }, 'denylist built; worker READY') break } catch (err) { @@ -98,6 +131,36 @@ export class DenylistManager { this.timer = setInterval(() => void this.refresh(), this.deps.refreshMs) // Don't keep the event loop alive solely for the refresh timer. this.timer.unref?.() + + // A separate, usually much shorter cadence for the feed alone — see refreshFeed below. + const feedMs = this.deps.feedRefreshMs + if (this.deps.refreshFeed && feedMs && feedMs < this.deps.refreshMs) { + this.feedTimer = setInterval(() => void this.refreshFeed(), feedMs) + this.feedTimer.unref?.() + } + } + + /** + * Pull the indexer feed into the store already in use. Never throws. + * + * Deliberately does NOT touch `builtAt`: the freshness gate is about the authoritative sources, + * and letting a cheap feed fetch reset it would keep a stale sanctions list looking current. + */ + async refreshFeed(): Promise { + if (this.stopped || !this.current || !this.deps.refreshFeed) return 0 + try { + const applied = await this.deps.refreshFeed(this.current) + if (applied > 0) { + this.publishMetrics() + // Debug, not info: this runs every few seconds and usually re-applies an unchanged feed. + // The indexer logs each publish, and `dvn_denylist_size` tracks the result. + this.deps.logger.debug({ entries: applied, size: this.current.size }, 'indexer feed refreshed') + } + return applied + } catch (err) { + this.deps.logger.warn({ err: (err as Error).message }, 'feed refresh failed; keeping current labels') + return 0 + } } /** Attempt a refresh. Returns whether it succeeded. Never throws. */ @@ -109,7 +172,7 @@ export class DenylistManager { const next = await this.build() this.adopt(next) this.deps.metrics.denylistRefreshTotal.inc({ result: 'success' }) - this.deps.logger.info({ size: next.size }, 'denylist refreshed') + this.deps.logger.info({ size: next.store.size, degraded: next.degraded }, 'denylist refreshed') return true } catch (err) { this.deps.metrics.denylistRefreshTotal.inc({ result: 'failure' }) @@ -124,17 +187,18 @@ export class DenylistManager { } } - /** Adopt a freshly built list, stamp the build time, publish metrics, recompute state. */ - private adopt(dl: Denylist): void { - this.current = dl + /** Adopt a freshly built store, stamp the build time, publish metrics, recompute state. */ + private adopt(build: RiskStoreBuild): void { + this.current = build.store this.builtAt = this.now() + this._degraded = build.degraded this.publishMetrics() this.evaluate() } /** - * Re-evaluate readiness against staleness. Called every loop tick and after every build. - * This is the single source of truth for the READY <-> HALTED transition. + * Re-evaluate readiness. Called every loop tick and after every build. This is the single + * source of truth for the READY <-> HALTED transition. */ evaluate(): DvnState { if (!this.current) { @@ -142,28 +206,57 @@ export class DenylistManager { return this._state } this.deps.metrics.denylistAgeSeconds.set(Math.floor(this.ageMs() / 1000)) - // Readiness is a pure function of freshness; REFRESHING is only a transient marker - // that refresh() sets and then resolves via this method. - this.setState(this.ageMs() > this.deps.maxStalenessMs ? 'HALTED' : 'READY') + for (const source of ['trusted_indexer']) { + this.deps.metrics.sourceDegraded.set({ source }, this._degraded.includes(source) ? 1 : 0) + } + + // Staleness first: an aged store is unsafe regardless of which sources built it. REFRESHING + // is only a transient marker that refresh() sets and then resolves via this method. + if (this.ageMs() > this.deps.maxStalenessMs) { + this.setState('HALTED', 'stale_denylist') + return this._state + } + if (this._degraded.length > 0 && this.degradedMode === 'halt') { + this.setState('HALTED', 'degraded_source') + return this._state + } + this.setState('READY') return this._state } + /** Every reason `halted` can carry, so exactly one is ever asserted at a time. */ + private static readonly HALT_REASONS = ['stale_denylist', 'degraded_source'] as const + + /** Assert one halt reason and clear the others; pass none to clear them all. */ + private publishHalted(active?: string): void { + for (const reason of DenylistManager.HALT_REASONS) { + this.deps.metrics.halted.set({ reason }, reason === active ? 1 : 0) + } + } + private setState(next: DvnState, haltReason = 'stale_denylist'): void { if (next === this._state) { - // Keep the halted gauge asserted while we remain halted. - if (next === 'HALTED') this.deps.metrics.halted.set({ reason: haltReason }, 1) + // Keep the gauge asserted while we remain halted — the reason can change without the + // state changing (a degraded store going on to age out, say). + if (next === 'HALTED') this.publishHalted(haltReason) return } const prev = this._state this._state = next if (next === 'HALTED') { - this.deps.metrics.halted.set({ reason: haltReason }, 1) + this.publishHalted(haltReason) this.deps.metrics.ready.set(0) this.deps.logger.error({ prev, reason: haltReason, ageMs: this.ageMs() }, 'fail-closed: HALTED — withholding all verification') } else if (next === 'READY') { - this.deps.metrics.halted.set({ reason: 'stale_denylist' }, 0) + this.publishHalted() this.deps.metrics.ready.set(1) if (prev === 'HALTED') this.deps.logger.warn({ prev }, 'recovered: READY — resuming verification') + if (this._degraded.length) { + this.deps.logger.warn( + { degraded: this._degraded }, + 'READY but DEGRADED — verifying on authoritative sources only; feed-derived labels are absent', + ) + } } else { this.deps.metrics.ready.set(0) } @@ -171,7 +264,7 @@ export class DenylistManager { private publishMetrics(): void { if (!this.current) return - for (const [source, count] of Object.entries(this.current.tagCounts())) { + for (const [source, count] of Object.entries(this.current.countsBySource())) { this.deps.metrics.denylistSize.set({ source }, count) } this.deps.metrics.denylistAgeSeconds.set(Math.floor(this.ageMs() / 1000)) @@ -180,6 +273,8 @@ export class DenylistManager { stop(): void { this.stopped = true if (this.timer) clearInterval(this.timer) + if (this.feedTimer) clearInterval(this.feedTimer) this.timer = undefined + this.feedTimer = undefined } } diff --git a/worker/runtime/errors.ts b/worker/runtime/errors.ts new file mode 100644 index 0000000..7388f35 --- /dev/null +++ b/worker/runtime/errors.ts @@ -0,0 +1,78 @@ +/** + * Compact rendering of chain errors for the log. + * + * ethers puts the entire transaction and receipt into `error.message`, so logging it verbatim buries + * one line of meaning under a kilobyte of hex that is already available from the tx hash. What an + * operator needs is the error class, the revert reason, and the transaction to look at. + */ + +/** + * Custom-error selectors worth naming, since a bare `0x4c3118d4` in a log is unreadable. + * + * Selector collisions across contracts are possible in principle; these are the ones this worker + * actually provokes, so the mapping is unambiguous in practice. + */ +const SELECTORS: Record = { + // Our DVN + '0x7c214f04': 'NotOperator', + '0x7f2e1049': 'NotSendLibrary', + '0x60df9f87': 'UnknownAction', + '0xa0940ea9': 'VerificationRequiresAllow', + '0x61c9fc06': 'AllowNotSeparatelyRecorded', + '0x118cdaa7': 'OwnableUnauthorizedAccount', + // LayerZero ULN / endpoint. LZ_ULN_Verifying doubles as "already committed" — committing deletes + // the attestation the ULN would otherwise find, so the two states share one error. + '0x4c3118d4': 'LZ_ULN_Verifying (or already committed)', + '0xc09b6350': 'LZ_InvalidNonce', + '0x0177e1ca': 'LZ_PathNotVerifiable', + '0xc9bf37b7': 'LZ_ULN_InvalidPacketHeader', + '0x3a9ae7b9': 'LZ_ULN_InvalidPacketVersion', + '0xb24ab92f': 'LZ_ULN_Unauthorized', + '0x6592671c': 'LZ_ULN_InvalidWorkerOptions', +} + +/** Revert data, wherever ethers nested it. Only accepted alongside a revert signal. */ +function revertData(err: unknown): string | undefined { + for (let e = err as Record | undefined, depth = 0; e && depth < 6; depth++) { + const data = e.data + // `code: 3` is the JSON-RPC execution-reverted code; requiring it (or an explicit reason) keeps + // calldata — which also lives under `data` on some layers — from being read as revert output. + const reverted = e.code === 3 || /execution reverted/i.test(String(e.reason ?? '')) + if (reverted && typeof data === 'string' && /^0x[0-9a-fA-F]{8}/.test(data)) return data.slice(0, 10).toLowerCase() + e = e.error as Record | undefined + } + return undefined +} + +/** The message up to the point ethers starts dumping structures into it. */ +function firstClause(message: string): string { + return message.split(/\s*(?:\[ See:|\(error=|\(transaction=|\(transactionHash=)/)[0].trim() +} + +/** + * One line: error class, named revert, and the transaction to inspect. + * + * Non-chain errors fall through to their own message, so a plain `new Error('rpc down')` reads the + * same as it always did. + */ +export function briefError(err: unknown): string { + if (!err || typeof err !== 'object') return String(err) + const e = err as Record + const bits: string[] = [] + + if (typeof e.code === 'string') bits.push(e.code) + + // Why it failed, best available: the named revert beats ethers' generic prose. The message clause + // is the fallback, not an addition — "cannot estimate gas; transaction may fail or may require + // manual gas limit" says nothing once `revert LZ_ULN_Verifying` is on the line. + const selector = revertData(err) + if (selector) bits.push(`revert ${SELECTORS[selector] ?? selector}`) + else if (typeof e.reason === 'string' && e.reason) bits.push(e.reason) + else bits.push(firstClause(String(e.message ?? ''))) + + const tx = e.transactionHash ?? e.receipt?.transactionHash + if (typeof tx === 'string') bits.push(`tx ${tx}`) + + const line = bits.filter(Boolean).join(' · ') + return line || String(err) +} diff --git a/worker/runtime/http.ts b/worker/runtime/http.ts index 8e399bb..9fc3713 100644 --- a/worker/runtime/http.ts +++ b/worker/runtime/http.ts @@ -8,6 +8,14 @@ export interface HttpServerDeps { metrics: Metrics /** Readiness predicate — true only when the worker is verifying (READY). */ isReady: () => boolean + /** + * Snapshot of the held-packet queue for the operator dashboard. Omit to disable GET /pending. + * Read-only: releasing a hold stays an on-chain owner action (`approvePacket`), never an + * HTTP call — an endpoint that could release holds would put owner authority on this port. + */ + pending?: () => unknown + /** Fail-closed state snapshot for the operator dashboard. Omit to disable GET /status. */ + status?: () => unknown logger: Logger } @@ -17,16 +25,21 @@ export interface HttpServerHandle { } /** - * Operational HTTP surface for Kubernetes + Prometheus: + * Operational HTTP surface for Kubernetes + Prometheus, plus read-only JSON for the dashboard: * GET /healthz liveness — 200 while the process is up (restart if it stops answering) * GET /readyz readiness — 200 only when verifying; 503 while INITIALIZING/HALTED so * traffic/alerts see the fail-closed state * GET /metrics Prometheus exposition + * GET /status fail-closed state snapshot (JSON) + * GET /pending held packets awaiting delay/approval (JSON) + * + * Every response carries a permissive CORS header: everything served here is read-only and + * non-secret, and the demo dashboard reads it straight from the browser. * * Deliberately framework-free: one tiny request router on node:http. */ function send(res: ServerResponse, status: number, contentType: string, body: string): void { - res.writeHead(status, { 'content-type': contentType }) + res.writeHead(status, { 'content-type': contentType, 'access-control-allow-origin': '*' }) res.end(body) } @@ -43,6 +56,12 @@ export function startHttpServer(deps: HttpServerDeps): Promise const body = await deps.metrics.registry.metrics() return send(res, 200, deps.metrics.registry.contentType, body) } + if (url === '/status' && deps.status) { + return send(res, 200, 'application/json', JSON.stringify(deps.status())) + } + if (url === '/pending' && deps.pending) { + return send(res, 200, 'application/json', JSON.stringify(deps.pending())) + } send(res, 404, 'text/plain', 'not found\n') } catch (err) { deps.logger.error({ err: (err as Error).message, url }, 'http handler error') @@ -57,7 +76,7 @@ export function startHttpServer(deps: HttpServerDeps): Promise server.listen(deps.port, () => { server.removeListener('error', reject) const port = (server.address() as AddressInfo).port - deps.logger.info({ port }, 'http server listening (/healthz /readyz /metrics)') + deps.logger.info({ port }, 'http server listening (/healthz /readyz /metrics /status /pending)') resolve({ port, close: () => diff --git a/worker/runtime/metrics.ts b/worker/runtime/metrics.ts index 3c3e186..f24c8cc 100644 --- a/worker/runtime/metrics.ts +++ b/worker/runtime/metrics.ts @@ -12,22 +12,30 @@ export interface Metrics { readonly ready: Gauge readonly halted: Gauge<'reason'> - // Denylist + // Denylist / risk store readonly denylistSize: Gauge<'source'> readonly denylistAgeSeconds: Gauge readonly denylistRefreshTotal: Counter<'result'> + readonly sourceDegraded: Gauge<'source'> + readonly feedRejectedTotal: Counter<'reason'> // Scan progress readonly chainHeadBlock: Gauge<'chain'> readonly checkpointBlock: Gauge<'chain'> readonly packetsScanned: Counter<'chain'> readonly packetsAssigned: Counter<'chain'> + readonly packetsUnparsed: Counter<'chain'> readonly scanErrors: Counter<'chain'> // Verification outcomes readonly verifications: Counter<'chain' | 'result'> readonly commits: Counter<'chain' | 'result'> - readonly vetoes: Counter<'chain' | 'tag'> + readonly deliveries: Counter<'chain' | 'result'> + readonly decisions: Counter<'chain' | 'action'> + readonly screeningEvidence: Counter<'type' | 'source'> + readonly pendingPackets: Gauge<'action'> + readonly approvals: Counter<'chain'> + readonly verdictRecords: Counter<'chain' | 'result'> readonly txSendSeconds: Histogram<'chain' | 'op'> } @@ -51,16 +59,40 @@ export function createMetrics(): Metrics { denylistSize: g('dvn_denylist_size', 'Denylist entries by source.', ['source']), denylistAgeSeconds: g('dvn_denylist_age_seconds', 'Seconds since the denylist was last built.'), denylistRefreshTotal: c('dvn_denylist_refresh_total', 'Denylist refresh attempts by result.', ['result']), + sourceDegraded: g('dvn_source_degraded', 'A tolerated source is unavailable (1) — its labels are absent.', ['source']), + feedRejectedTotal: c('dvn_feed_rejected_total', 'Indexer feeds rejected, by reason.', ['reason']), chainHeadBlock: g('dvn_chain_head_block', 'Latest block height observed per chain.', ['chain']), checkpointBlock: g('dvn_checkpoint_block', 'Last scanned block persisted per chain.', ['chain']), packetsScanned: c('dvn_packets_scanned_total', 'PacketSent events scanned per chain.', ['chain']), packetsAssigned: c('dvn_packets_assigned_total', 'Packets assigned to our DVN per chain.', ['chain']), + packetsUnparsed: c( + 'dvn_packets_unparsed_total', + 'PacketSent events skipped as undecodable — normally other OApps on the shared endpoint.', + ['chain'], + ), scanErrors: c('dvn_scan_errors_total', 'Scan/RPC errors per chain.', ['chain']), verifications: c('dvn_verifications_total', 'submitVerification calls by result.', ['chain', 'result']), commits: c('dvn_commits_total', 'commitVerification calls by result.', ['chain', 'result']), - vetoes: c('dvn_vetoes_total', 'Sanctioned transfers withheld, by tag.', ['chain', 'tag']), + deliveries: c( + 'dvn_deliveries_total', + 'lzReceive calls by result — the delivery the executor does not perform for custom DVNs.', + ['chain', 'result'], + ), + decisions: c('dvn_decisions_total', 'Risk verdicts by action (allow/delay/manual-review/block).', ['chain', 'action']), + screeningEvidence: c( + 'dvn_screening_evidence_total', + 'Evidence records observed while screening packets, by label type and source — which risk signals actually fire.', + ['type', 'source'], + ), + pendingPackets: g('dvn_pending_packets', 'Packets currently held, by action.', ['action']), + approvals: c('dvn_approvals_total', 'Owner approvals of held packets observed on-chain.', ['chain']), + verdictRecords: c( + 'dvn_verdict_records_total', + 'Separate recordVerdict transactions by result. A failure means the outcome was enforced but not recorded.', + ['chain', 'result'], + ), txSendSeconds: new Histogram({ name: 'dvn_tx_send_seconds', help: 'On-chain transaction send+mine latency (seconds).', diff --git a/worker/runtime/scanner.ts b/worker/runtime/scanner.ts index aa4efc5..58fb492 100644 --- a/worker/runtime/scanner.ts +++ b/worker/runtime/scanner.ts @@ -3,9 +3,12 @@ import type { Metrics } from './metrics' import type { ResolvedChain } from './config' import type { DvnState } from './denylist-manager' import type { ParsedPacket } from '../chain/events' -import type { Assessor } from '../assess/assess' +import type { Assessor, Assessment } from '../assess/assess' import { combine } from '../assess/assess' -import { Checkpoint } from '../checkpoint' +import { DELAY_POLICY, type RiskAction } from '../assess/policy' +import { encodeVerdict, type OnChainVerdict } from '../assess/verdict' +import { Checkpoint, type DeferredAction, type DeferredRecord, type Party } from '../checkpoint' +import { briefError } from './errors' /** The slice of an ethers provider the scanner needs (kept tiny for testability). */ export interface BlockHeightSource { @@ -15,27 +18,32 @@ export interface BlockHeightSource { export interface ScanChainDeps { chain: ResolvedChain provider: BlockHeightSource + /** Blocks to stay behind the head while scanning — NOT the value attested on-chain. */ confirmations: number /** How far back to backfill on a cold checkpoint. */ scanWindow: number + /** Maximum blocks per getLogs range. */ + scanChunk: number /** Current fail-closed state — verification proceeds only when READY. */ state: () => DvnState checkpoint: Checkpoint scanAssigned: (fromBlock: number, toBlock: number) => Promise> scanPackets: (fromBlock: number, toBlock: number) => Promise + scanApproved: (fromBlock: number, toBlock: number) => Promise> handlePacket: (p: ParsedPacket) => Promise metrics: Metrics logger: Logger } /** - * Scan one chain once: read the safe head, and only when READY, process packets assigned to - * our DVN and advance the checkpoint. + * Scan one chain once: read the safe head, and only when READY, record owner approvals, + * process packets assigned to our DVN, and advance the checkpoint. * * Fail-closed freeze: when not READY we return without scanning OR advancing the checkpoint, * so every packet in the unscreened window is processed once the worker recovers — no packet - * passes the verification window unscreened. Errors are counted and rethrown so the caller - * can isolate one chain's failure from the others. + * passes the verification window unscreened. Approvals ride the same cursor, so a freeze + * delays them rather than losing them. Errors are counted and rethrown so the caller can + * isolate one chain's failure from the others. */ export async function scanChainOnce(deps: ScanChainDeps): Promise { const { chain, metrics, logger } = deps @@ -45,66 +53,349 @@ export async function scanChainOnce(deps: ScanChainDeps): Promise { metrics.chainHeadBlock.set({ chain: chain.key }, head) const safeHead = head - deps.confirmations - let from = deps.checkpoint.getLastBlock(chain.key) - if (from === 0) from = Math.max(0, safeHead - deps.scanWindow) - if (safeHead <= from) return + let cursor = deps.checkpoint.getLastBlock(chain.key) + if (cursor === 0) cursor = Math.max(0, safeHead - deps.scanWindow) + if (safeHead <= cursor) return // FAIL-CLOSED FREEZE: do not scan, verify, or advance while not READY. if (deps.state() !== 'READY') { - log.warn({ state: deps.state(), from, safeHead }, 'not READY — freezing checkpoint, withholding verification') + log.warn({ state: deps.state(), from: cursor, safeHead }, 'not READY — freezing checkpoint, withholding verification') return } - const [assigned, packets] = await Promise.all([ - deps.scanAssigned(from + 1, safeHead), - deps.scanPackets(from + 1, safeHead), - ]) - metrics.packetsScanned.inc({ chain: chain.key }, packets.length) - - for (const p of packets) { - // Re-check at packet granularity: the denylist can age into HALTED during the awaits - // above (TOCTOU). If so, abort WITHOUT advancing the checkpoint so the window stays - // frozen and every packet is re-screened once we recover. - if (deps.state() !== 'READY') { - log.warn({ state: deps.state() }, 'state changed mid-scan — aborting; checkpoint frozen') - return + // Bounded chunks, and the checkpoint advances per chunk. Public RPCs cap getLogs ranges + // (2000 blocks is typical), and the freeze above deliberately holds the checkpoint through an + // outage — so the gap on recovery can exceed that cap. Querying it whole would fail every + // tick while the gap only grew, wedging the worker permanently instead of catching up. + while (cursor < safeHead) { + const from = cursor + 1 + const to = Math.min(from + deps.scanChunk - 1, safeHead) + + const [assigned, packets, approved] = await Promise.all([ + deps.scanAssigned(from, to), + deps.scanPackets(from, to), + deps.scanApproved(from, to), + ]) + metrics.packetsScanned.inc({ chain: chain.key }, packets.length) + + for (const payloadHash of approved) { + deps.checkpoint.addApproval(payloadHash) + metrics.approvals.inc({ chain: chain.key }) + log.warn({ payloadHash }, 'owner approval observed on-chain') } - if (!assigned.has(p.payloadHash.toLowerCase())) continue - metrics.packetsAssigned.inc({ chain: chain.key }) - await deps.handlePacket(p) - } - deps.checkpoint.setLastBlock(chain.key, safeHead) - deps.checkpoint.save() - metrics.checkpointBlock.set({ chain: chain.key }, safeHead) + for (const p of packets) { + // Re-check at packet granularity: the risk store can age into HALTED during the awaits + // above (TOCTOU). If so, abort WITHOUT advancing the checkpoint so the window stays + // frozen and every packet is re-screened once we recover. + if (deps.state() !== 'READY') { + log.warn({ state: deps.state() }, 'state changed mid-scan — aborting; checkpoint frozen') + return + } + if (!assigned.has(p.payloadHash.toLowerCase())) continue + metrics.packetsAssigned.inc({ chain: chain.key }) + await deps.handlePacket(p) + } + + deps.checkpoint.setLastBlock(chain.key, to) + deps.checkpoint.save() + metrics.checkpointBlock.set({ chain: chain.key }, to) + cursor = to + } } catch (err) { metrics.scanErrors.inc({ chain: chain.key }) throw err } } -export interface VerifyPacketDeps { +/** + * How far a packet has already got on the destination, independent of who took it there. + * + * - `pending` nothing has committed it; a commit failure here is a real failure + * - `committed` the payload is committed and waiting to be executed + * - `cleared` the slot has moved on — executed, or abandoned by a skip + */ +export type CommitState = 'pending' | 'committed' | 'cleared' + +/** Everything both the live path and the deferred-queue path need. */ +export interface ProcessDeferredDeps { assessor: Assessor resolveDst: (eid: number) => ResolvedChain | undefined - verify: (dst: ResolvedChain, header: string, payloadHash: string) => Promise + verify: ( + dst: ResolvedChain, + header: string, + payloadHash: string, + verdict: OnChainVerdict, + ) => Promise commit: (dst: ResolvedChain, header: string, payloadHash: string) => Promise + /** + * Deliver the message by running `lzReceive`. Optional: omitted, the packet is still verified and + * committed, and delivery waits on whatever executor is watching the pathway. + */ + execute?: (dst: ResolvedChain, header: string, guid: string, message: string) => Promise + /** + * Emit a verdict for a packet that was not verified. Optional: omitted, the outcome is still + * enforced and simply not recorded on-chain. + */ + recordVerdict?: (dst: ResolvedChain, payloadHash: string, verdict: OnChainVerdict) => Promise + /** Which non-allow actions get a separate `recordVerdict` transaction. */ + emitVerdictFor?: ReadonlySet + /** + * Whether the owner has abandoned a held packet by skipping its nonce. Optional: omitted, a + * rejected packet simply stays in the queue being re-screened. + */ + abandoned?: (dst: ResolvedChain, header: string) => Promise + /** + * What the destination already knows about a packet, used to tell a lost race from a real + * failure. Optional: omitted, a race is reported as a failure — noisy, but never wrong about + * enforcement. + */ + commitState?: (dst: ResolvedChain, header: string, payloadHash: string) => Promise checkpoint: Checkpoint metrics: Metrics logger: Logger + now?: () => number +} + +export interface VerifyPacketDeps extends ProcessDeferredDeps { + srcChainKey: string +} + +/** What a decision is applied to, independent of whether it arrived live or from the queue. */ +interface Subject { + key: string + payloadHash: string + header: string + dstEid: number + dst: ResolvedChain srcChainKey: string + parties: Party[] + /** Needed to deliver the message; absent for holds persisted before delivery was driven here. */ + guid?: string + message?: string +} + +/** Screen every party concurrently and fold the results into one verdict. */ +function screen(parties: Party[], assessor: Assessor): Promise { + return Promise.all(parties.map((p) => assessor(p.subject, p.chainKey))).then(combine) +} + +/** Count each evidence record a screening produced, so the dashboard shows which signals fire. */ +function countEvidence(verdict: Assessment, metrics: Metrics): void { + for (const e of verdict.evidence) metrics.screeningEvidence.inc({ type: e.type, source: e.source }) +} + +function publishPending(deps: ProcessDeferredDeps): void { + const counts = deps.checkpoint.deferredCounts() + for (const [action, n] of Object.entries(counts)) deps.metrics.pendingPackets.set({ action }, n) +} + +/** Encode the verdict for the chain, logging any reason code that has no assigned bit. */ +function encodeFor( + s: Subject, + verdict: Assessment, + log: Logger, + opts: { overrideAction?: RiskAction; extraReasons?: string[] } = {}, +): OnChainVerdict { + const { encoded, unmapped } = encodeVerdict(s.payloadHash, verdict, s.parties, opts) + if (unmapped.length) { + log.warn({ unmapped }, 'reason codes have no reasonMask bit — recorded as unmapped; add them to REASON_BITS') + } + return encoded } /** - * Screen one packet and act on the verdict: - * - sanctioned -> VETO (withhold verification), mark processed so we don't reconsider it - * - clean -> submit verification, then drive commit so the message can be delivered + * Submit verification, then drive commit so the default executor delivers the message (it does + * not commit for custom DVNs). Verification is recorded as processed only after it lands + * on-chain; commit is best-effort, so a commit failure does not undo the verify. + * + * The verdict travels with `submitVerification` at no extra transaction cost, so an allowed + * packet always carries an on-chain reason for having been allowed. * - * Verification is recorded as processed only after it lands on-chain; commit is best-effort - * (the executor or a later run can commit), so a commit failure does not undo the verify. + * Returns whether the verification landed. On failure the caller must leave the packet + * somewhere the next tick can find it — the scan cursor advances past its block, so a packet + * that is neither processed nor deferred is lost, not retried. + */ +async function submitAndCommit( + s: Subject, + verdict: Assessment, + deps: ProcessDeferredDeps, + log: Logger, + opts: { overrideAction?: RiskAction; extraReasons?: string[] } = {}, +): Promise { + // The contract accepts only ACTION_ALLOW on a verification, so a release keeps its reasons + // while reporting the action actually taken. + const encoded = encodeFor(s, verdict, log, { overrideAction: 'allow', ...opts }) + try { + const verifyTx = await deps.verify(s.dst, s.header, s.payloadHash, encoded) + deps.metrics.verifications.inc({ chain: s.srcChainKey, result: 'success' }) + log.info({ tx: verifyTx, dst: s.dst.key }, 'VERIFY submitted') + deps.checkpoint.markProcessed(s.key) + deps.checkpoint.save() + publishPending(deps) + } catch (err) { + deps.metrics.verifications.inc({ chain: s.srcChainKey, result: 'failure' }) + log.error({ err: briefError(err) }, 'submitVerification failed; packet stays held for retry') + return false + } + + try { + const commitTx = await deps.commit(s.dst, s.header, s.payloadHash) + deps.metrics.commits.inc({ chain: s.srcChainKey, result: 'success' }) + log.info({ tx: commitTx, dst: s.dst.key }, 'COMMIT driven') + } catch (err) { + // The LayerZero executor watches this pathway too and sometimes commits first. When it does, + // our commit reverts with the same `LZ_ULN_Verifying` the ULN uses for "not verified yet" — + // committing consumes the attestation from storage, so the two are indistinguishable from the + // error alone. Asking the destination what state the packet is in tells them apart. + const state = await commitStateOf(s, deps, log) + if (state === 'pending' || state === 'unknown') { + deps.metrics.commits.inc({ chain: s.srcChainKey, result: 'failure' }) + log.warn( + { err: briefError(err), state }, + 'commit pending (verified on-chain; executor/next run may commit)', + ) + // Nothing to deliver until the packet is committed. + return true + } + deps.metrics.commits.inc({ chain: s.srcChainKey, result: 'raced' }) + log.info({ state, dst: s.dst.key }, 'COMMIT already done by the executor') + if (state === 'cleared') return true // executed as well — nothing left to deliver + } + + // Delivery. Committing only makes the packet executable — for a custom DVN pathway no executor + // runs `lzReceive`, so the message would sit undelivered. Best-effort like the commit: the + // enforcement decision is already settled, and a failure here costs delivery latency, not safety. + if (deps.execute && s.guid && s.message) { + try { + const tx = await deps.execute(s.dst, s.header, s.guid, s.message) + deps.metrics.deliveries.inc({ chain: s.srcChainKey, result: 'success' }) + log.info({ tx, dst: s.dst.key }, 'DELIVERED (lzReceive driven)') + } catch (err) { + // Same race, one step later: the executor may have run `lzReceive` between our commit and + // this call, which leaves the nonce cleared and our call reverting. + if ((await commitStateOf(s, deps, log)) === 'cleared') { + deps.metrics.deliveries.inc({ chain: s.srcChainKey, result: 'raced' }) + log.info({ dst: s.dst.key }, 'DELIVERED by the executor') + } else { + deps.metrics.deliveries.inc({ chain: s.srcChainKey, result: 'failure' }) + log.warn( + { err: briefError(err) }, + 'lzReceive failed (committed on-chain; an executor or a later run may still deliver)', + ) + } + } + } + return true +} + +/** The destination's own account of a packet. `unknown` when it cannot be read — never assumed. */ +async function commitStateOf( + s: Subject, + deps: ProcessDeferredDeps, + log: Logger, +): Promise { + if (!deps.commitState) return 'unknown' + try { + return await deps.commitState(s.dst, s.header, s.payloadHash) + } catch (err) { + log.debug({ err: briefError(err) }, 'could not read the packet state on the destination') + return 'unknown' + } +} + +/** Withhold the packet and record why, so the next tick can reconsider it. */ +function hold( + s: Subject, + verdict: Assessment, + action: DeferredAction, + attempts: number, + deps: ProcessDeferredDeps, + log: Logger, +): void { + const now = (deps.now ?? Date.now)() + const existing = deps.checkpoint.getDeferred(s.key) + const record: DeferredRecord = { + payloadHash: s.payloadHash, + dstEid: s.dstEid, + header: s.header, + // Carried so a released packet can still be delivered: by then its source block is far behind + // the scan cursor and the message cannot be read again. + guid: s.guid, + message: s.message, + srcChainKey: s.srcChainKey, + parties: s.parties, + action, + score: verdict.score, + reasonCodes: verdict.reasonCodes, + attempts, + // A manual-review hold is released by an on-chain approval, never by the clock. + retryAfter: action === 'delay' ? now + DELAY_POLICY.retryAfterMs : Number.MAX_SAFE_INTEGER, + firstDeferredAt: existing?.firstDeferredAt ?? now, + } + deps.checkpoint.defer(s.key, record) + deps.checkpoint.save() + publishPending(deps) + log.warn( + { action, score: verdict.score, reasonCodes: verdict.reasonCodes, attempts }, + action === 'manual-review' + ? 'WITHHELD — awaiting owner approval (approvePacket)' + : 'WITHHELD — will re-screen after delay', + ) +} + +/** + * Refuse the packet for good. Withholding verification IS the veto; nothing is submitted. + * + * The refusal is settled locally BEFORE any attempt to record it on-chain, and the recording is + * best-effort. Enforcement must not depend on a transaction succeeding — a failed record costs + * an audit entry, whereas a failed enforcement would let the packet through. The trade is + * deliberate: `dvn_verdict_records_total{result="failure"}` counts what was lost. + */ +async function veto(s: Subject, verdict: Assessment, deps: ProcessDeferredDeps, log: Logger): Promise { + log.warn( + { score: verdict.score, reasonCodes: verdict.reasonCodes }, + 'VETO — withholding verification for sanctioned transfer', + ) + deps.checkpoint.markProcessed(s.key) + deps.checkpoint.save() + publishPending(deps) + await recordOutcome(s, verdict, deps, log) +} + +/** Emit a verdict for a packet that was not verified, if configured and available. */ +async function recordOutcome( + s: Subject, + verdict: Assessment, + deps: ProcessDeferredDeps, + log: Logger, +): Promise { + if (!deps.recordVerdict || !deps.emitVerdictFor?.has(verdict.action)) return + try { + const tx = await deps.recordVerdict(s.dst, s.payloadHash, encodeFor(s, verdict, log)) + deps.metrics.verdictRecords.inc({ chain: s.srcChainKey, result: 'success' }) + log.info({ tx, action: verdict.action }, 'verdict recorded on-chain') + } catch (err) { + deps.metrics.verdictRecords.inc({ chain: s.srcChainKey, result: 'failure' }) + log.error( + { err: briefError(err), action: verdict.action }, + 'verdict record FAILED — outcome is still enforced, but this decision has no on-chain record', + ) + } +} + +/** + * Screen one freshly scanned packet and act on the verdict: + * - allow -> submit verification, then drive commit + * - delay -> withhold, re-screen after DELAY_POLICY.retryAfterMs + * - manual-review -> withhold until the owner calls approvePacket + * - block -> veto, settled for good */ export async function verifyPacket(p: ParsedPacket, deps: VerifyPacketDeps): Promise { const key = `${p.payloadHash}:${p.dstEid}` if (deps.checkpoint.isProcessed(key)) return + // Already held: the deferred queue owns its lifecycle, including re-screening. + if (deps.checkpoint.getDeferred(key)) return const dst = deps.resolveDst(p.dstEid) if (!dst) { @@ -113,42 +404,187 @@ export async function verifyPacket(p: ParsedPacket, deps: VerifyPacketDeps): Pro } const log = deps.logger.child({ chain: deps.srcChainKey, payloadHash: p.payloadHash }) - const verdict = combine([ - deps.assessor(p.senderAddress), - deps.assessor(p.receiverAddress), - deps.assessor(p.oft.toAddress), - ]) + // The sender is an OApp on the source chain; the receiver OApp and the OFT recipient are on + // the destination. Contract checks read chain state, so each party carries its own chain. + const parties: Party[] = [ + { subject: p.senderAddress, chainKey: deps.srcChainKey }, + { subject: p.receiverAddress, chainKey: dst.key }, + { subject: p.oft.toAddress, chainKey: dst.key }, + ] + const verdict = await screen(parties, deps.assessor) + countEvidence(verdict, deps.metrics) + const s: Subject = { + key, + payloadHash: p.payloadHash, + header: p.header, + dstEid: p.dstEid, + dst, + srcChainKey: deps.srcChainKey, + parties, + guid: p.guid, + message: p.message, + } - if (verdict.blocked) { - for (const tag of verdict.tags.length ? verdict.tags : ['unknown']) { - deps.metrics.vetoes.inc({ chain: deps.srcChainKey, tag }) + deps.metrics.decisions.inc({ chain: deps.srcChainKey, action: verdict.action }) + switch (verdict.action) { + case 'allow': { + if (await submitAndCommit(s, verdict, deps, log)) return + // The send failed after screening said allow. Defer rather than drop: the scan cursor + // advances past this packet's block, so without a deferred record it would never be + // presented again. The delay queue re-screens and re-sends until it lands (or, if the + // failure persists past DELAY_POLICY.maxAttempts, escalates to a human). + hold(s, verdict, 'delay', 0, deps, log) + return } - log.warn({ reasons: verdict.reasons, tags: verdict.tags }, 'VETO — withholding verification for sanctioned transfer') - deps.checkpoint.markProcessed(key) - deps.checkpoint.save() - return + case 'block': + return veto(s, verdict, deps, log) + default: + hold(s, verdict, verdict.action, 0, deps, log) + return recordOutcome(s, verdict, deps, log) } +} +/** + * Drop a held packet whose nonce the owner skipped, and report whether it was dropped. + * + * This is the owner's refusal, observed rather than obeyed: skipping is signed on the endpoint by + * the OApp's delegate, and the endpoint enforces it — the worker has no say and no key for it. All + * that is left here is to stop carrying a packet that can never be delivered. + * + * A failed read leaves the packet in the queue. Guessing "abandoned" from an RPC error would throw + * away a legitimate hold, which is the one outcome that cannot be undone. + */ +async function dropIfAbandoned( + key: string, + rec: DeferredRecord, + dst: ResolvedChain, + deps: ProcessDeferredDeps, + log: Logger, +): Promise { + if (!deps.abandoned) return false try { - const verifyTx = await deps.verify(dst, p.header, p.payloadHash) - deps.metrics.verifications.inc({ chain: deps.srcChainKey, result: 'success' }) - log.info({ tx: verifyTx, dst: dst.key }, 'VERIFY submitted') - deps.checkpoint.markProcessed(key) - deps.checkpoint.save() + if (!(await deps.abandoned(dst, rec.header))) return false } catch (err) { - deps.metrics.verifications.inc({ chain: deps.srcChainKey, result: 'failure' }) - log.error({ err: (err as Error).message }, 'submitVerification failed; will retry next scan') - return + log.warn({ err: briefError(err) }, 'could not check whether the packet was skipped; keeping the hold') + return false } - // Drive commit so the default executor delivers the message (it does not commit for - // custom DVNs). Best-effort: verification is already on-chain. - try { - const commitTx = await deps.commit(dst, p.header, p.payloadHash) - deps.metrics.commits.inc({ chain: deps.srcChainKey, result: 'success' }) - log.info({ tx: commitTx, dst: dst.key }, 'COMMIT driven') - } catch (err) { - deps.metrics.commits.inc({ chain: deps.srcChainKey, result: 'failure' }) - log.warn({ err: (err as Error).message }, 'commit pending (verified on-chain; executor/next run may commit)') + deps.checkpoint.clearDeferred(key) + deps.checkpoint.markProcessed(key) + deps.checkpoint.save() + publishPending(deps) + deps.metrics.decisions.inc({ chain: rec.srcChainKey, action: 'rejected' }) + log.warn( + { heldMs: (deps.now ?? Date.now)() - rec.firstDeferredAt, action: rec.action }, + 'owner REJECTED — nonce skipped on the destination; dropping the held packet', + ) + return true +} + +/** + * Reconsider every held packet once. Runs independently of the per-chain scan cursor, so a + * hold is never lost by the checkpoint advancing past the block it came from. + * + * Two things release a hold: the clock (a `delay` whose `retryAfter` has passed is re-screened + * against the current risk store) and an owner approval observed on-chain. A `delay` that + * keeps scoring `delay` escalates to `manual-review` once `DELAY_POLICY.maxAttempts` is + * exhausted, so nothing loops forever waiting for a signal that is not coming. + * + * `dvn_decisions_total` counts terminal outcomes and action changes only — a retry that lands + * on the same action is not a new decision, or a single delayed packet would look like eight. + */ +export async function processDeferred(deps: ProcessDeferredDeps): Promise { + const now = (deps.now ?? Date.now)() + + for (const [key, rec] of deps.checkpoint.deferredEntries()) { + if (deps.checkpoint.isProcessed(key)) { + deps.checkpoint.clearDeferred(key) + continue + } + const dst = deps.resolveDst(rec.dstEid) + if (!dst) { + deps.logger.warn({ dstEid: rec.dstEid, payloadHash: rec.payloadHash }, 'held packet: unknown destination EID') + continue + } + const log = deps.logger.child({ chain: rec.srcChainKey, payloadHash: rec.payloadHash }) + + // Checked ahead of the retry gate, and ahead of approval: a manual-review hold is otherwise + // skipped outright here, and rejection is exactly the decision an operator makes about one. + // + // A rejected packet must leave the queue rather than be re-screened forever — and not only for + // tidiness. Holds are released when a re-screen comes back clean, so a packet the owner refused + // would be released the moment its risk label expired, into a channel that can no longer carry + // it. Dropping it makes the refusal stick. + if (await dropIfAbandoned(key, rec, dst, deps, log)) continue + + const approved = deps.checkpoint.isApproved(rec.payloadHash) + if (!approved && (rec.action === 'manual-review' || rec.retryAfter > now)) continue + const verdict = await screen(rec.parties, deps.assessor) + countEvidence(verdict, deps.metrics) + const s: Subject = { + key, + payloadHash: rec.payloadHash, + header: rec.header, + dstEid: rec.dstEid, + dst, + srcChainKey: rec.srcChainKey, + parties: rec.parties, + guid: rec.guid, + message: rec.message, + } + + if (approved) { + // An approval overrides a hold, not a refusal. If the packet now scores `block` — a + // direct sanctions hit landed after the operator signed off — the refusal wins. + if (verdict.action === 'block') { + deps.metrics.decisions.inc({ chain: rec.srcChainKey, action: 'block' }) + log.error( + { score: verdict.score, reasonCodes: verdict.reasonCodes }, + 'approval REFUSED — packet now scores block; vetoing despite owner approval', + ) + await veto(s, verdict, deps, log) + continue + } + deps.metrics.decisions.inc({ chain: rec.srcChainKey, action: 'allow' }) + log.warn({ heldMs: now - rec.firstDeferredAt }, 'owner-approved — releasing held packet') + // The hold is dropped by markProcessed once verification lands, never before: if the + // send fails here, the record must survive or the packet is lost — its source block is + // long behind the scan cursor and cannot be re-read. + // + // Recorded as `allow` with `owner_approved` in the mask: the action taken was to allow it, + // and the audit trail should say a human did that rather than a re-screen. + await submitAndCommit(s, verdict, deps, log, { extraReasons: ['owner_approved'] }) + continue + } + + if (verdict.action === 'allow') { + deps.metrics.decisions.inc({ chain: rec.srcChainKey, action: 'allow' }) + log.info({ attempts: rec.attempts }, 're-screened clean — releasing held packet') + if (await submitAndCommit(s, verdict, deps, log)) continue + // The send failed: fall through to the retry accounting below, so a destination chain + // that stays unreachable escalates to a human instead of retrying forever. + } else if (verdict.action === 'block') { + deps.metrics.decisions.inc({ chain: rec.srcChainKey, action: 'block' }) + await veto(s, verdict, deps, log) + continue + } else if (verdict.action === 'manual-review') { + const changed = rec.action !== 'manual-review' + if (changed) deps.metrics.decisions.inc({ chain: rec.srcChainKey, action: 'manual-review' }) + hold(s, verdict, 'manual-review', rec.attempts, deps, log) + // Only a change of action is a new decision; a retry landing on the same one is not, so + // a held packet does not re-emit the same verdict every tick. + if (changed) await recordOutcome(s, verdict, deps, log) + continue + } + + const attempts = rec.attempts + 1 + if (attempts >= DELAY_POLICY.maxAttempts) { + deps.metrics.decisions.inc({ chain: rec.srcChainKey, action: DELAY_POLICY.escalateTo }) + log.warn({ attempts, escalateTo: DELAY_POLICY.escalateTo }, 'delay exhausted — escalating') + hold(s, verdict, DELAY_POLICY.escalateTo as DeferredAction, attempts, deps, log) + await recordOutcome(s, { ...verdict, action: DELAY_POLICY.escalateTo }, deps, log) + continue + } + hold(s, verdict, 'delay', attempts, deps, log) } } diff --git a/worker/runtime/tx-sender.ts b/worker/runtime/tx-sender.ts index fe96926..ef45683 100644 --- a/worker/runtime/tx-sender.ts +++ b/worker/runtime/tx-sender.ts @@ -1,5 +1,6 @@ import type { Logger } from 'pino' import type { Metrics } from './metrics' +import { briefError } from './errors' /** Minimal shape of a submitted transaction we depend on (a subset of ethers' response). */ export interface SubmittedTx { @@ -38,10 +39,19 @@ const RETRIABLE_CODES = new Set([ ]) const NONCE_CODES = new Set(['NONCE_EXPIRED']) const RETRIABLE_MESSAGE = /(timeout|timed out|underpriced|replacement|nonce|econnreset|etimedout|socket hang up|503|502|rate.?limit)/i +/** + * A gas estimate can fail two ways that ethers reports under one code. + * + * `UNPREDICTABLE_GAS_LIMIT` covers both a flaky node and a call the chain simply rejects. Only the + * first is worth retrying: a reverting call reverts at any gas price, so escalating gas through + * every attempt just spends the backoff and reports a determined outcome as a transient one. + */ +const REVERTED = /execution reverted/i /** True for transient infra/gas/nonce errors that a bump-and-retry can plausibly fix. */ export function isRetriableTxError(err: unknown): boolean { const e = err as { code?: string; message?: string } + if (e?.code === 'UNPREDICTABLE_GAS_LIMIT' && REVERTED.test(e.message ?? '')) return false if (e?.code && RETRIABLE_CODES.has(e.code)) return true if (e?.code === 'CALL_EXCEPTION') return false // on-chain revert — retrying won't help return !!e?.message && RETRIABLE_MESSAGE.test(e.message) @@ -102,7 +112,7 @@ export class TxSender { // Drop the cached nonce so the next send re-syncs from the chain. this.trackedNonce = undefined log.error( - { err: (err as Error).message, attempt, retriable, nonce }, + { err: briefError(err), attempt, retriable, nonce }, 'transaction send failed', ) throw err @@ -113,7 +123,7 @@ export class TxSender { } const backoff = this.baseBackoffMs * 2 ** attempt log.warn( - { err: (err as Error).message, attempt, nextNonce: nonce, gasPrice: gasPrice.toString(), backoffMs: backoff }, + { err: briefError(err), attempt, nextNonce: nonce, gasPrice: gasPrice.toString(), backoffMs: backoff }, 'transaction send failed; retrying with higher gas', ) await this.sleep(backoff) diff --git a/worker/service.ts b/worker/service.ts index 92d969b..2d0b257 100644 --- a/worker/service.ts +++ b/worker/service.ts @@ -8,11 +8,19 @@ import { DenylistManager } from './runtime/denylist-manager' import { TxSender } from './runtime/tx-sender' import { Lifecycle } from './runtime/lifecycle' import { createActions } from './runtime/actions' -import { scanChainOnce, verifyPacket } from './runtime/scanner' -import { scanPacketSent, scanJobAssigned } from './chain/events' +import { scanChainOnce, verifyPacket, processDeferred } from './runtime/scanner' +import { scanPacketSent, scanJobAssigned, scanPacketApproved } from './chain/events' +import { ethersReader } from './chain/reader' +import { RpcContractInspector, type ChainReader } from './assess/providers/contract' +import { RpcTokenInspector } from './assess/providers/token' +import { buildRiskStore, refreshFeedInto } from './assess/assess' +import type { RiskAction } from './assess/policy' +import { FeedError } from './assess/ingest/feed' import { Checkpoint } from './checkpoint' +import { briefError } from './runtime/errors' const SCAN_WINDOW = Number(process.env.SCAN_BACKFILL_BLOCKS || 50) +const SCAN_CHUNK = Number(process.env.SCAN_CHUNK_BLOCKS || 2000) /** Sleep that resolves early when the abort signal fires (for prompt shutdown). */ function interruptibleSleep(ms: number, signal: AbortSignal): Promise { @@ -29,13 +37,20 @@ function interruptibleSleep(ms: number, signal: AbortSignal): Promise { } async function main(): Promise { - const config: Config = loadConfig() + // forbidOwnerKeys: the service must never share an environment with an owner-capable key — + // the owner key is what approves the packets this process withholds. + const config: Config = loadConfig(process.env, { forbidOwnerKeys: true }) const logger = createLogger(config) const metrics = createMetrics() metrics.up.set(1) logger.info( - { chains: config.chains.map((c) => c.key), pollMs: config.pollMs, confirmations: config.confirmations }, + { + chains: config.chains.map((c) => c.key), + pollMs: config.pollMs, + confirmations: config.confirmations, + scanConfirmations: config.scanConfirmations, + }, 'compliance DVN worker starting', ) @@ -45,7 +60,7 @@ async function main(): Promise { const senders: Record = {} for (const chain of config.chains) { const provider = new ethers.providers.JsonRpcProvider(chain.rpc) - const signer = new ethers.Wallet(config.privateKey, provider) + const signer = new ethers.Wallet(config.operatorPrivateKey, provider) providers[chain.key] = provider signers[chain.key] = signer senders[chain.key] = new TxSender({ @@ -60,19 +75,67 @@ async function main(): Promise { }) } + // Live chain checks, one reader per chain (a packet's parties span both sides). + const readers: Record = {} + for (const chain of config.chains) readers[chain.key] = ethersReader(providers[chain.key]) + const riskProviders = { + contracts: new RpcContractInspector({ readers }), + tokens: new RpcTokenInspector({ readers }), + } + + // The checkpoint doubles as the feed's replay-protection store, so it must exist before the + // first build rather than after it. + const checkpoint = new Checkpoint(config.checkpointPath) + + const feed = config.indexerFeedUrl + ? { url: config.indexerFeedUrl, signers: [...config.indexerSigners], maxSkewSec: config.feedMaxSkewSec } + : undefined + if (feed) { + logger.info({ url: feed.url, signers: feed.signers.length, degradedMode: config.degradedMode }, 'indexer feed enabled') + } else { + logger.warn('no INDEXER_FEED_URL — running on authoritative sources only, no graph labels') + } + + // Shared by the full rebuild and the feed-only refresh, so both apply the same replay protection + // and report a rejection the same way. + const feedOptions = { + feed, + feedDeps: { + versions: { + get: (source: string) => checkpoint.getFeedVersion(source), + set: (source: string, version: number) => { + checkpoint.setFeedVersion(source, version) + checkpoint.save() + }, + }, + }, + onDegraded: (source: string, err: Error) => { + const reason = err instanceof FeedError ? err.reason : 'fetch_failed' + metrics.feedRejectedTotal.inc({ reason }) + logger.error({ source, reason, err: err.message }, 'risk source unavailable — running degraded') + }, + } + // Denylist lifecycle (fail-closed state machine). const denylist = new DenylistManager({ + build: () => buildRiskStore(feedOptions), + // A newly published graph label should be usable in seconds; re-downloading the sanctions + // lists that often would not be. + refreshFeed: (store) => refreshFeedInto(store, feedOptions), refreshMs: config.denylistRefreshMs, + feedRefreshMs: config.feedRefreshMs, maxStalenessMs: config.maxDenylistStalenessMs, + degradedMode: config.degradedMode, + providers: riskProviders, logger, metrics, }) await denylist.start() - - const checkpoint = new Checkpoint(config.checkpointPath) const actions = createActions(signers, senders, config.confirmations) const byEid = new Map(config.chains.map((c) => [c.eid, c])) const resolveDst = (eid: number) => byEid.get(eid) + const emitVerdictFor = new Set(config.emitVerdictFor) + logger.info({ emitVerdictFor: config.emitVerdictFor }, 'verdict events: allow always rides on submitVerification') // Lifecycle + control plane. const abort = new AbortController() @@ -89,7 +152,25 @@ async function main(): Promise { }) const isReady = () => running && denylist.state === 'READY' - const http = await startHttpServer({ port: config.httpPort, metrics, isReady, logger }) + const http = await startHttpServer({ + port: config.httpPort, + metrics, + isReady, + status: () => ({ + state: denylist.state, + degraded: [...denylist.degraded], + denylistAgeMs: denylist.ageMs(), + chains: config.chains.map((c) => ({ key: c.key, eid: c.eid, dvn: c.dvn })), + }), + pending: () => ({ + pending: checkpoint.deferredEntries().map(([key, r]) => ({ + key, + ...r, + approved: checkpoint.isApproved(r.payloadHash), + })), + }), + logger, + }) lifecycle.onShutdown(() => http.close()) lifecycle.install() @@ -102,18 +183,28 @@ async function main(): Promise { await scanChainOnce({ chain, provider: providers[chain.key], - confirmations: config.confirmations, + confirmations: config.scanConfirmations, scanWindow: SCAN_WINDOW, + scanChunk: SCAN_CHUNK, state: () => denylist.state, checkpoint, scanAssigned: (from, to) => scanJobAssigned(providers[chain.key], chain.dvn, from, to), - scanPackets: (from, to) => scanPacketSent(providers[chain.key], chain.endpoint, from, to), + scanPackets: (from, to) => + scanPacketSent(providers[chain.key], chain.endpoint, from, to, (payloadHash, reason) => { + metrics.packetsUnparsed.inc({ chain: chain.key }) + logger.debug({ chain: chain.key, payloadHash, reason }, 'skipped undecodable packet (not an OFT transfer)') + }), + scanApproved: (from, to) => scanPacketApproved(providers[chain.key], chain.dvn, from, to), handlePacket: (p) => verifyPacket(p, { assessor: denylist.assessor(), resolveDst, verify: actions.verify, commit: actions.commit, + recordVerdict: actions.recordVerdict, + execute: actions.execute, + commitState: actions.commitState, + emitVerdictFor, checkpoint, metrics, logger, @@ -124,9 +215,35 @@ async function main(): Promise { }) } catch (err) { // Per-chain isolation: one chain's RPC failure must not stop the others. - logger.error({ chain: chain.key, err: (err as Error).message }, 'scan failed; will retry next tick') + logger.error({ chain: chain.key, err: briefError(err) }, 'scan failed; will retry next tick') + } + } + + // Reconsider held packets after scanning, so an approval seen this tick is acted on in it. + // Only when READY — re-screening needs a fresh risk store just as first screening does. + if (running && denylist.state === 'READY') { + try { + await processDeferred({ + assessor: denylist.assessor(), + resolveDst, + verify: actions.verify, + commit: actions.commit, + recordVerdict: actions.recordVerdict, + execute: actions.execute, + commitState: actions.commitState, + // Only the deferred pass needs this: a packet is screened live before anyone could have + // rejected it, so the check would be a wasted read on the hot path. + abandoned: actions.abandoned, + emitVerdictFor, + checkpoint, + metrics, + logger, + }) + } catch (err) { + logger.error({ err: briefError(err) }, 'deferred-queue pass failed; will retry next tick') } } + if (running) await interruptibleSleep(config.pollMs, abort.signal) } diff --git a/worker/test/assess.spec.ts b/worker/test/assess.spec.ts index 8a9ebbe..d13ad85 100644 --- a/worker/test/assess.spec.ts +++ b/worker/test/assess.spec.ts @@ -1,30 +1,445 @@ -import { describe, it, expect } from 'vitest' -import { makeAssessor, combine } from '../assess/assess' -import { Denylist } from '../assess/store' - -describe('assess + combine', () => { - const dl = new Denylist() - dl.add('0x00000000000000000000000000000000000000aa', 'ofac', 'sdn') - const assess = makeAssessor(dl) - - it('flags a denylisted address as blocked', () => { - const r = assess('0x00000000000000000000000000000000000000AA') - expect(r.blocked).toBe(true) - expect(r.tags).toContain('ofac') +import { describe, it, expect, vi } from 'vitest' +import { makeAssessor, combine, CONTRACT_CHECK_UNAVAILABLE, TOKEN_CHECK_UNAVAILABLE } from '../assess/assess' +import { RiskStore } from '../assess/store' +import type { ContractFacts, ContractInspector } from '../assess/providers/contract' +import type { TokenFacts, TokenInspector, TokenResolution } from '../assess/providers/token' + +const A = '0x00000000000000000000000000000000000000aa' +const B = '0x00000000000000000000000000000000000000bb' +const ADMIN = '0x00000000000000000000000000000000000000cc' +const TOKEN = '0x00000000000000000000000000000000000000dd' +const REAL_USDC = '0x036cbd53842c5426634e7929541ec2318f3dcf7e' // canonical on baseSepolia + +/** An inspector returning fixed facts, or throwing to simulate an unreachable RPC. */ +function inspector(facts: ContractFacts | Error): ContractInspector { + return { + inspect: async () => { + if (facts instanceof Error) throw facts + return facts + }, + } +} + +/** A token inspector with a fixed resolution and fixed metadata. */ +function tokenInspector(resolution: TokenResolution, facts?: TokenFacts | Error): TokenInspector { + return { + resolveToken: async () => resolution, + inspect: async () => { + if (facts instanceof Error) throw facts + return facts ?? { address: TOKEN } + }, + } +} + +const EOA: ContractFacts = { isContract: false, proxy: false } +const CONTRACT: ContractFacts = { isContract: true, proxy: false } + +describe('assess', () => { + it('BLOCKS a direct sanctions hit', async () => { + const store = new RiskStore() + store.upsert({ subject: A, labels: ['sanctions'], source: 'ofac' }) + const r = await makeAssessor(store)(A.toUpperCase()) + expect(r.action).toBe('block') expect(r.score).toBe(100) + expect(r.reasonCodes).toContain('sanctions') + expect(r.evidence[0]).toMatchObject({ type: 'sanctions', source: 'ofac', weight: 100 }) }) - it('passes a clean address', () => { - const r = assess('0x00000000000000000000000000000000000000bb') - expect(r.blocked).toBe(false) + it('ALLOWS a clean address with no evidence', async () => { + const r = await makeAssessor(new RiskStore())(B) + expect(r.action).toBe('allow') expect(r.score).toBe(0) + expect(r.evidence).toEqual([]) + }) + + it('sends a 1-hop label to manual-review, not block', async () => { + const store = new RiskStore() + store.upsert({ subject: A, labels: ['sanctions_1hop'], source: 'trusted_indexer' }) + const r = await makeAssessor(store)(A) + expect(r.score).toBe(70) + expect(r.action).toBe('manual-review') + }) + + it('grades graph proximity by depth: 2 hops delays, 3 hops alone allows', async () => { + const two = new RiskStore() + two.upsert({ subject: A, labels: ['sanctions_2hop'], source: 'trusted_indexer' }) + const r2 = await makeAssessor(two)(A) + expect(r2.score).toBe(45) + expect(r2.action).toBe('delay') + + const three = new RiskStore() + three.upsert({ subject: A, labels: ['sanctions_3hop'], source: 'trusted_indexer' }) + const r3 = await makeAssessor(three)(A) + expect(r3.score).toBe(25) + expect(r3.action).toBe('allow') // context on its own, never an action + }) + + it('lets distant proximity combine with other signals', async () => { + const store = new RiskStore() + // 25 + 35 = 60 -> manual-review; neither label alone reaches it. + store.upsert({ subject: A, labels: ['sanctions_3hop', 'mixer_exposure_2hop'], source: 'trusted_indexer' }) + const r = await makeAssessor(store)(A) + expect(r.score).toBe(60) + expect(r.action).toBe('manual-review') + }) + + it('delays a weak contract signal', async () => { + const store = new RiskStore() + store.upsert({ subject: A, subjectType: 'contract', labels: ['contract_admin_risk'], source: 'trusted_indexer' }) + const r = await makeAssessor(store)(A) + expect(r.score).toBe(50) + expect(r.action).toBe('delay') + }) + + it('caps a public_event source at delay even when its score reaches 100', async () => { + const store = new RiskStore() + store.upsert({ subject: A, labels: ['sanctions'], source: 'public_event' }) + const r = await makeAssessor(store)(A) + expect(r.score).toBe(100) // score is unclamped... + expect(r.action).toBe('delay') // ...but an untrusted source cannot cause a refusal + }) + + it('caps own_verdict_event at manual-review (no self-reinforcing block)', async () => { + const store = new RiskStore() + store.upsert({ subject: A, labels: ['sanctions'], source: 'own_verdict_event' }) + expect((await makeAssessor(store)(A)).action).toBe('manual-review') + }) + + it('lets a trusted source in the same evidence set restore the block ceiling', async () => { + const store = new RiskStore() + store.upsert({ subject: A, labels: ['sanctions'], source: 'public_event' }) + store.upsert({ subject: A, labels: ['sanctions'], source: 'ofac' }) + expect((await makeAssessor(store)(A)).action).toBe('block') + }) + + it('sums labels toward a higher action', async () => { + const store = new RiskStore() + // 20 + 15 = 35 -> delay; neither label alone reaches the delay threshold. + store.upsert({ subject: A, labels: ['unverified_contract', 'upgradeable_proxy'], source: 'trusted_indexer' }) + const r = await makeAssessor(store)(A) + expect(r.score).toBe(35) + expect(r.action).toBe('delay') + }) + + it('keeps two separate source claims adding up', async () => { + const store = new RiskStore() + store.upsert({ subject: A, labels: ['unverified_contract'], source: 'trusted_indexer' }) // 20 + store.upsert({ subject: A, labels: ['upgradeable_proxy'], source: 'operator' }) // 15 + const r = await makeAssessor(store)(A) + expect(r.score).toBe(35) // separate claims still add up + expect(r.action).toBe('delay') + }) + + it('does not let one source talk its own labels down with a low asserted score', async () => { + const store = new RiskStore() + store.upsert({ subject: A, labels: ['sanctions'], source: 'ofac', score: 5 }) + expect((await makeAssessor(store)(A)).score).toBe(100) // max(100, 5) + }) + + it('ignores an expired entry rather than scoring it', async () => { + let t = 1000 + const store = new RiskStore({ now: () => t }) + store.upsert({ subject: A, labels: ['sanctions'], source: 'trusted_indexer', expiresAt: 2000 }) + expect((await makeAssessor(store)(A)).action).toBe('block') + t = 2001 + expect((await makeAssessor(store)(A)).action).toBe('allow') + }) +}) + +/** + * Only a direct hit justifies an automatic refusal. Derived signals sum toward the score as + * normal, but however high they stack they escalate to a human instead of blocking. + */ +describe('block requires a direct hit', () => { + it('sends stacked derived labels to manual-review, not block', async () => { + const store = new RiskStore() + store.upsert({ subject: A, labels: ['sanctions_1hop', 'mixer_exposure'], source: 'trusted_indexer' }) + const r = await makeAssessor(store)(A) + expect(r.score).toBe(100) // the risk total is reported honestly... + expect(r.action).toBe('manual-review') // ...but an inference does not auto-refuse + }) + + it('still blocks a direct sanctions hit', async () => { + const store = new RiskStore() + store.upsert({ subject: A, labels: ['sanctions'], source: 'ofac' }) + expect((await makeAssessor(store)(A)).action).toBe('block') + }) + + it('blocks when a direct hit accompanies derived labels', async () => { + const store = new RiskStore() + store.upsert({ subject: A, labels: ['sanctions_1hop', 'sanctioned_mixer'], source: 'trusted_indexer' }) + expect((await makeAssessor(store)(A)).action).toBe('block') + }) + + it('blocks a confirmed scam token', async () => { + const store = new RiskStore() + store.upsert({ subject: A, subjectType: 'token', labels: ['scam_token'], source: 'operator' }) + expect((await makeAssessor(store)(A)).action).toBe('block') + }) + + // The hole that makes the check per-claim rather than global: neither claim alone is grounds + // for a refusal, so combining them must not produce one. + it('does not combine an untrusted direct hit with a trusted derived label into a block', async () => { + const store = new RiskStore() + store.upsert({ subject: A, labels: ['sanctions'], source: 'public_event' }) // direct but untrusted + store.upsert({ subject: A, labels: ['sanctions_1hop'], source: 'ofac' }) // trusted but derived + const r = await makeAssessor(store)(A) + expect(r.score).toBe(100) + expect(r.action).toBe('manual-review') + }) + + it('an asserted score cannot manufacture a direct hit', async () => { + const store = new RiskStore() + store.upsert({ subject: A, labels: ['unverified_contract'], source: 'trusted_indexer', score: 100 }) + const r = await makeAssessor(store)(A) + expect(r.score).toBe(100) + expect(r.action).toBe('manual-review') + }) + + it('leaves the delay and allow bands untouched', async () => { + const store = new RiskStore() + store.upsert({ subject: A, labels: ['contract_admin_risk'], source: 'trusted_indexer' }) + expect((await makeAssessor(store)(A)).action).toBe('delay') // 50 + const clean = new RiskStore() + clean.upsert({ subject: A, labels: ['upgradeable_proxy'], source: 'trusted_indexer' }) + expect((await makeAssessor(clean)(A)).action).toBe('allow') // 15 + }) + + it('keeps the source ceiling as the tighter of the two caps', async () => { + const store = new RiskStore() + // Direct hit, but from a source that may never refuse on its own. + store.upsert({ subject: A, labels: ['sanctions'], source: 'public_event' }) + expect((await makeAssessor(store)(A)).action).toBe('delay') + }) + + it('ignores an expired entry rather than scoring it (regression guard)', async () => { + let t = 1000 + const store = new RiskStore({ now: () => t }) + store.upsert({ subject: A, labels: ['sanctions'], source: 'trusted_indexer', expiresAt: 2000 }) + expect((await makeAssessor(store)(A)).action).toBe('block') + t = 2001 + expect((await makeAssessor(store)(A)).action).toBe('allow') + }) +}) + +describe('assess with contract checks', () => { + it('skips live checks entirely when no chainKey is given', async () => { + const inspect = vi.fn() + const assess = makeAssessor(new RiskStore(), { + contracts: { inspect }, + tokens: tokenInspector({ kind: 'token', address: TOKEN }), + }) + const r = await assess(A) // no chainKey + expect(inspect).not.toHaveBeenCalled() + expect(r.action).toBe('allow') + expect(r.reasonCodes).toEqual([]) + }) + + it('adds no evidence for an EOA', async () => { + const assess = makeAssessor(new RiskStore(), { contracts: inspector(EOA) }) + const r = await assess(A, 'baseSepolia') + expect(r.action).toBe('allow') + expect(r.evidence).toEqual([]) + }) + + it('labels an upgradeable proxy and records the implementation', async () => { + const assess = makeAssessor(new RiskStore(), { + contracts: inspector({ isContract: true, proxy: true, implementation: B }), + }) + const r = await assess(A, 'baseSepolia') + expect(r.reasonCodes).toEqual(['upgradeable_proxy']) + expect(r.score).toBe(15) + expect(r.action).toBe('allow') // 15 alone is below the delay threshold + expect(r.evidence[0].details).toMatchObject({ implementation: B }) + }) + + it('flags admin risk when the controller itself carries labels', async () => { + const store = new RiskStore() + store.upsert({ subject: ADMIN, labels: ['sanctions'], source: 'ofac' }) + const assess = makeAssessor(store, { + contracts: inspector({ isContract: true, proxy: true, controller: ADMIN }), + }) + const r = await assess(A, 'baseSepolia') + // upgradeable_proxy (15) + contract_admin_risk (50) = 65 -> manual-review + expect(r.reasonCodes.sort()).toEqual(['contract_admin_risk', 'upgradeable_proxy']) + expect(r.score).toBe(65) + expect(r.action).toBe('manual-review') + const admin = r.evidence.find((e) => e.type === 'contract_admin_risk')! + expect(admin.details).toMatchObject({ controller: ADMIN, controllerLabels: ['sanctions'] }) + }) + + it('does not flag admin risk for a clean controller', async () => { + const assess = makeAssessor(new RiskStore(), { + contracts: inspector({ isContract: true, proxy: false, controller: ADMIN }), + }) + const r = await assess(A, 'baseSepolia') + expect(r.reasonCodes).toEqual([]) + expect(r.action).toBe('allow') + }) + + it('FAILS CLOSED to delay when the contract check is unavailable', async () => { + const assess = makeAssessor(new RiskStore(), { contracts: inspector(new Error('rpc timeout')) }) + const r = await assess(A, 'baseSepolia') + expect(r.action).toBe('delay') + expect(r.reasonCodes).toContain(CONTRACT_CHECK_UNAVAILABLE) + expect(r.evidence[r.evidence.length - 1]).toMatchObject({ type: CONTRACT_CHECK_UNAVAILABLE, weight: 0 }) + }) + + it('keeps a block verdict when the contract check is unavailable (never downgrades)', async () => { + const store = new RiskStore() + store.upsert({ subject: A, labels: ['sanctions'], source: 'ofac' }) + const assess = makeAssessor(store, { contracts: inspector(new Error('rpc timeout')) }) + expect((await assess(A, 'baseSepolia')).action).toBe('block') + }) + + it('does not let an unavailable check inflate a score into a block', async () => { + const store = new RiskStore() + store.upsert({ subject: A, labels: ['sanctions_1hop'], source: 'trusted_indexer' }) + const assess = makeAssessor(store, { contracts: inspector(new Error('rpc timeout')) }) + const r = await assess(A, 'baseSepolia') + expect(r.score).toBe(70) // unchanged — the failure contributes no weight + expect(r.action).toBe('manual-review') + }) +}) + +describe('assess with token checks', () => { + it('does nothing for an OApp that is not an OFT', async () => { + const assess = makeAssessor(new RiskStore(), { + contracts: inspector(CONTRACT), + tokens: tokenInspector({ kind: 'not-a-token' }), + }) + const r = await assess(A, 'baseSepolia') + expect(r.action).toBe('allow') + expect(r.evidence).toEqual([]) + }) + + it('skips token resolution for a known EOA', async () => { + const resolveToken = vi.fn() + const assess = makeAssessor(new RiskStore(), { + contracts: inspector(EOA), + tokens: { resolveToken, inspect: async () => ({ address: TOKEN }) }, + }) + await assess(A, 'baseSepolia') + expect(resolveToken).not.toHaveBeenCalled() + }) + + it('BLOCKS an OFT moving a curated scam token, naming the token as the subject', async () => { + const store = new RiskStore() + store.upsert({ subject: TOKEN, subjectType: 'token', labels: ['scam_token'], source: 'operator' }) + const assess = makeAssessor(store, { + contracts: inspector(CONTRACT), + tokens: tokenInspector({ kind: 'token', address: TOKEN }), + }) + const r = await assess(A, 'baseSepolia') + expect(r.action).toBe('block') + expect(r.score).toBe(100) + // The verdict is about the OApp, but the evidence points at the token itself. + expect(r.subject).toBe(A) + expect(r.evidence[0]).toMatchObject({ type: 'scam_token', subject: TOKEN }) + }) + + it('flags a stablecoin impersonator', async () => { + const assess = makeAssessor(new RiskStore(), { + contracts: inspector(CONTRACT), + tokens: tokenInspector({ kind: 'token', address: TOKEN }, { address: TOKEN, symbol: 'USDC', decimals: 6 }), + }) + const r = await assess(A, 'baseSepolia') + expect(r.reasonCodes).toEqual(['fake_stablecoin_suspect']) + expect(r.score).toBe(65) + expect(r.action).toBe('manual-review') + expect(r.evidence[0]).toMatchObject({ subject: TOKEN, details: { symbol: 'USDC' } }) + }) + + it('does NOT flag the canonical stablecoin', async () => { + const assess = makeAssessor(new RiskStore(), { + contracts: inspector(CONTRACT), + tokens: tokenInspector( + { kind: 'token', address: REAL_USDC }, + { address: REAL_USDC, symbol: 'USDC', decimals: 6 }, + ), + }) + expect((await assess(A, 'baseSepolia')).action).toBe('allow') + }) + + it('does not judge a watched symbol on a chain with no canonical address', async () => { + const assess = makeAssessor(new RiskStore(), { + contracts: inspector(CONTRACT), + tokens: tokenInspector({ kind: 'token', address: TOKEN }, { address: TOKEN, symbol: 'USDC', decimals: 6 }), + }) + expect((await assess(A, 'someUnlistedChain')).action).toBe('allow') + }) + + it('FAILS CLOSED when token resolution is unknown (transport failure, not a revert)', async () => { + const assess = makeAssessor(new RiskStore(), { + contracts: inspector(CONTRACT), + tokens: tokenInspector({ kind: 'unknown', reason: 'rpc timeout' }), + }) + const r = await assess(A, 'baseSepolia') + expect(r.action).toBe('delay') + expect(r.reasonCodes).toContain(TOKEN_CHECK_UNAVAILABLE) + }) + + it('FAILS CLOSED when token metadata cannot be read', async () => { + const assess = makeAssessor(new RiskStore(), { + contracts: inspector(CONTRACT), + tokens: tokenInspector({ kind: 'token', address: TOKEN }, new Error('rpc down')), + }) + const r = await assess(A, 'baseSepolia') + expect(r.action).toBe('delay') + expect(r.reasonCodes).toContain(TOKEN_CHECK_UNAVAILABLE) + }) + + it('still blocks a curated scam token when metadata is unreadable', async () => { + const store = new RiskStore() + store.upsert({ subject: TOKEN, subjectType: 'token', labels: ['scam_token'], source: 'operator' }) + const assess = makeAssessor(store, { + contracts: inspector(CONTRACT), + tokens: tokenInspector({ kind: 'token', address: TOKEN }, new Error('rpc down')), + }) + // The store label needs no RPC, so the refusal survives the failed metadata read. + expect((await assess(A, 'baseSepolia')).action).toBe('block') + }) + + it('reports both failures when contract and token checks are down', async () => { + const assess = makeAssessor(new RiskStore(), { + contracts: inspector(new Error('rpc timeout')), + tokens: tokenInspector({ kind: 'unknown', reason: 'rpc timeout' }), + }) + const r = await assess(A, 'baseSepolia') + expect(r.reasonCodes.sort()).toEqual([CONTRACT_CHECK_UNAVAILABLE, TOKEN_CHECK_UNAVAILABLE].sort()) + expect(r.action).toBe('delay') + expect(r.score).toBe(0) + }) +}) + +describe('combine', () => { + const store = new RiskStore() + store.upsert({ subject: A, labels: ['sanctions'], source: 'ofac' }) + store.upsert({ subject: B, labels: ['sanctions_1hop'], source: 'trusted_indexer' }) + const assess = makeAssessor(store) + + it('takes the worst action across parties', async () => { + const [clean, b, a] = await Promise.all([assess('0x' + '9'.repeat(40)), assess(B), assess(A)]) + expect(combine([clean, clean]).action).toBe('allow') + expect(combine([clean, b]).action).toBe('manual-review') + expect(combine([clean, b, a]).action).toBe('block') + }) + + it('maxes scores instead of summing them, so two review parties are not a block', async () => { + const b = await assess(B) + const both = combine([b, b]) + expect(both.score).toBe(70) + expect(both.action).toBe('manual-review') + }) + + it('unions reason codes and keeps every piece of evidence', async () => { + const c = combine(await Promise.all([assess(A), assess(B)])) + expect(c.reasonCodes.sort()).toEqual(['sanctions', 'sanctions_1hop']) + expect(c.evidence.length).toBe(2) }) - it('combine blocks if any party is blocked', () => { - const clean = assess('0x00000000000000000000000000000000000000bb') - const bad = assess('0x00000000000000000000000000000000000000aa') - expect(combine([clean, clean]).blocked).toBe(false) - expect(combine([clean, bad, clean]).blocked).toBe(true) - expect(combine([clean, bad]).reasons.length).toBeGreaterThan(0) + it('allows an empty party list', () => { + expect(combine([]).action).toBe('allow') + expect(combine([]).score).toBe(0) }) }) diff --git a/worker/test/checkpoint.spec.ts b/worker/test/checkpoint.spec.ts index a0e17f6..4068235 100644 --- a/worker/test/checkpoint.spec.ts +++ b/worker/test/checkpoint.spec.ts @@ -17,4 +17,15 @@ describe('Checkpoint', () => { expect(c2.isProcessed('0xpackethash')).toBe(true) expect(c2.isProcessed('0xother')).toBe(false) }) + + // The processed set is a dedupe cache, not a ledger: without a bound it grows with every + // packet ever screened and is rewritten to disk on each save. + it('evicts the oldest processed keys past the cap', () => { + const c = new Checkpoint(PATH) + const cap = 50_000 + for (let i = 0; i <= cap; i++) c.markProcessed(`key-${i}`) + expect(c.isProcessed('key-0')).toBe(false) // oldest evicted + expect(c.isProcessed('key-1')).toBe(true) + expect(c.isProcessed(`key-${cap}`)).toBe(true) // newest kept + }) }) diff --git a/worker/test/config.spec.ts b/worker/test/config.spec.ts index 9954a00..2682080 100644 --- a/worker/test/config.spec.ts +++ b/worker/test/config.spec.ts @@ -7,7 +7,7 @@ const ADDR = '0x' + 'a'.repeat(40) /** Minimal env that should validate cleanly: PK + a DVN address for every enabled chain. */ function baseEnv(overrides: Record = {}): Record { return { - PRIVATE_KEY: PK, + OPERATOR_PRIVATE_KEY: PK, DVN_BASE_SEPOLIA: ADDR, DVN_OPTIMISM_SEPOLIA: ADDR, ...overrides, @@ -17,7 +17,7 @@ function baseEnv(overrides: Record = {}): Record { it('loads a valid config with defaults applied', () => { const cfg = loadConfig(baseEnv()) - expect(cfg.privateKey).toBe(PK) + expect(cfg.operatorPrivateKey).toBe(PK) expect(cfg.pollMs).toBe(15000) expect(cfg.confirmations).toBe(5) expect(cfg.maxDenylistStalenessMs).toBe(3_600_000) @@ -30,22 +30,28 @@ describe('loadConfig', () => { expect(base.endpoint).toMatch(/^0x[0-9a-fA-F]{40}$/) }) - it('rejects a missing private key', () => { - expect(() => loadConfig(baseEnv({ PRIVATE_KEY: undefined }))).toThrowError(/PRIVATE_KEY/) + it('rejects a missing operator key', () => { + expect(() => loadConfig(baseEnv({ OPERATOR_PRIVATE_KEY: undefined }))).toThrowError(/OPERATOR_PRIVATE_KEY/) }) - it('rejects a malformed private key', () => { - expect(() => loadConfig(baseEnv({ PRIVATE_KEY: '0xdeadbeef' }))).toThrowError(/PRIVATE_KEY/) + it('accepts a bare 64-hex operator key and normalizes it to the 0x form', () => { + // ethers accepts either notation, so rejecting the bare form would block a working config. + const bare = '1'.repeat(64) + expect(loadConfig(baseEnv({ OPERATOR_PRIVATE_KEY: bare })).operatorPrivateKey).toBe(`0x${bare}`) + }) + + it('rejects a malformed operator key', () => { + expect(() => loadConfig(baseEnv({ OPERATOR_PRIVATE_KEY: '0xdeadbeef' }))).toThrowError(/OPERATOR_PRIVATE_KEY/) }) it('aggregates multiple errors into one message', () => { let msg = '' try { - loadConfig({ PRIVATE_KEY: 'nope', POLL_MS: 'abc' }) + loadConfig({ OPERATOR_PRIVATE_KEY: 'nope', POLL_MS: 'abc' }) } catch (e) { msg = (e as Error).message } - expect(msg).toMatch(/PRIVATE_KEY/) + expect(msg).toMatch(/OPERATOR_PRIVATE_KEY/) expect(msg).toMatch(/POLL_MS/) // both DVN addresses also missing for the enabled chains expect(msg).toMatch(/DVN_BASE_SEPOLIA/) @@ -62,7 +68,7 @@ describe('loadConfig', () => { it('requires a DVN address only for enabled chains', () => { // optimism disabled -> its missing DVN address is not an error - const cfg = loadConfig({ PRIVATE_KEY: PK, DVN_BASE_SEPOLIA: ADDR, CHAINS_ENABLED: 'baseSepolia' }) + const cfg = loadConfig({ OPERATOR_PRIVATE_KEY: PK, DVN_BASE_SEPOLIA: ADDR, CHAINS_ENABLED: 'baseSepolia' }) expect(cfg.chains).toHaveLength(1) }) @@ -80,9 +86,118 @@ describe('loadConfig', () => { expect(() => loadConfig(baseEnv({ POLL_MS: '0' }))).toThrowError(/POLL_MS/) }) + /** + * Two different things wore one name. The attested value must satisfy the pathway's ULN + * `confirmations` or the destination refuses the packet; how far behind the head we scan is only + * a latency choice. Tying them together meant lowering latency broke verification. + */ + describe('scan depth vs attested confirmations', () => { + it('defaults the scan depth to the attested value', () => { + const cfg = loadConfig(baseEnv({ DVN_CONFIRMATIONS: '5' })) + expect(cfg.confirmations).toBe(5) + expect(cfg.scanConfirmations).toBe(5) + }) + + it('lets the scan run closer to the head without lowering what is attested', () => { + const cfg = loadConfig(baseEnv({ DVN_CONFIRMATIONS: '5', SCAN_CONFIRMATIONS: '1' })) + expect(cfg.confirmations).toBe(5) + expect(cfg.scanConfirmations).toBe(1) + }) + + it('accepts a scan depth of zero', () => { + expect(loadConfig(baseEnv({ SCAN_CONFIRMATIONS: '0' })).scanConfirmations).toBe(0) + }) + }) + it('validates max staleness is at least one refresh interval', () => { expect(() => loadConfig(baseEnv({ DENYLIST_REFRESH_MS: '600000', MAX_DENYLIST_STALENESS_MS: '300000' })), ).toThrowError(/MAX_DENYLIST_STALENESS_MS/) }) + + it('leaves the indexer feed disabled by default', () => { + const cfg = loadConfig(baseEnv()) + expect(cfg.indexerFeedUrl).toBe('') + expect(cfg.indexerSigners).toEqual([]) + expect(cfg.degradedMode).toBe('degrade') + expect(cfg.feedMaxSkewSec).toBe(300) + }) + + it('parses the feed URL and lowercases the signer allowlist', () => { + const cfg = loadConfig( + baseEnv({ + INDEXER_FEED_URL: 'https://indexer.test/feed.json', + INDEXER_SIGNERS: `${ADDR.toUpperCase().replace('0X', '0x')}, ${'0x' + 'b'.repeat(40)}`, + DEGRADED_MODE: 'halt', + }), + ) + expect(cfg.indexerFeedUrl).toBe('https://indexer.test/feed.json') + expect(cfg.indexerSigners).toEqual([ADDR, '0x' + 'b'.repeat(40)]) + expect(cfg.degradedMode).toBe('halt') + }) + + // An ingested-but-unverified feed is worse than no feed at all, so refuse to boot. + it('requires a signer allowlist when a feed URL is set', () => { + expect(() => loadConfig(baseEnv({ INDEXER_FEED_URL: 'https://indexer.test/feed.json' }))).toThrowError( + /INDEXER_SIGNERS/, + ) + }) + + it('rejects a malformed signer address', () => { + expect(() => + loadConfig(baseEnv({ INDEXER_FEED_URL: 'https://indexer.test/feed.json', INDEXER_SIGNERS: '0xnope' })), + ).toThrowError(/INDEXER_SIGNERS/) + }) + + it('ignores signers when no feed URL is set', () => { + expect(() => loadConfig(baseEnv({ INDEXER_SIGNERS: '0xnope' }))).not.toThrow() + }) + + /** + * The repo root's .env uses PRIVATE_KEY for the OWNER key. Copying it here would previously + * have started the worker with owner rights, letting it approve the packets it withheld. + */ + it('names the cause when the root .env was copied here', () => { + let msg = '' + try { + loadConfig({ PRIVATE_KEY: PK, DVN_BASE_SEPOLIA: ADDR, DVN_OPTIMISM_SEPOLIA: ADDR }) + } catch (e) { + msg = (e as Error).message + } + expect(msg).toMatch(/OPERATOR_PRIVATE_KEY/) + expect(msg).toMatch(/OWNER key/) + expect(msg).toMatch(/approves held packets/) + }) + + it('does not complain about PRIVATE_KEY when the operator key is set', () => { + // A stray PRIVATE_KEY in the shell is not itself a problem; only its use as the signer is. + expect(() => loadConfig(baseEnv({ PRIVATE_KEY: '0x' + '9'.repeat(64) }))).not.toThrow() + }) + + /** + * The service refuses owner-capable keys even alongside a valid operator key: the owner key + * approves the very packets the worker withholds, so the two must never share an environment. + * Default options keep accepting them — deploy scripts and one-off shells legitimately hold one. + */ + describe('forbidOwnerKeys (service mode)', () => { + it('refuses PRIVATE_KEY even when the operator key is also set', () => { + expect(() => + loadConfig(baseEnv({ PRIVATE_KEY: '0x' + '9'.repeat(64) }), { forbidOwnerKeys: true }), + ).toThrowError(/PRIVATE_KEY must not be set in the worker service's environment/) + }) + + it('refuses OWNER_PRIVATE_KEY', () => { + expect(() => + loadConfig(baseEnv({ OWNER_PRIVATE_KEY: '0x' + '9'.repeat(64) }), { forbidOwnerKeys: true }), + ).toThrowError(/OWNER_PRIVATE_KEY must not be set/) + }) + + it('boots normally when only the operator key is present', () => { + expect(() => loadConfig(baseEnv(), { forbidOwnerKeys: true })).not.toThrow() + }) + }) + + it('rejects an unknown degraded mode', () => { + expect(() => loadConfig(baseEnv({ DEGRADED_MODE: 'ignore' }))).toThrowError(/DEGRADED_MODE/) + }) }) diff --git a/worker/test/contract-provider.spec.ts b/worker/test/contract-provider.spec.ts new file mode 100644 index 0000000..f648626 --- /dev/null +++ b/worker/test/contract-provider.spec.ts @@ -0,0 +1,149 @@ +import { describe, it, expect, vi } from 'vitest' +import { RpcContractInspector, type ChainReader } from '../assess/providers/contract' + +const ADDR = '0x' + 'a'.repeat(40) +const IMPL = '0x' + 'b'.repeat(40) +const ADMIN = '0x' + 'c'.repeat(40) +const OWNER = '0x' + 'd'.repeat(40) + +const SLOT_IMPLEMENTATION = '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' +const SLOT_ADMIN = '0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103' +const EMPTY_WORD = '0x' + '0'.repeat(64) + +/** Right-align an address into a 32-byte word, the way storage and returndata hold it. */ +const word = (addr: string) => '0x' + addr.replace(/^0x/, '').padStart(64, '0') + +function reader(overrides: Partial = {}): ChainReader { + return { + getCode: async () => '0x60006000', + getStorageAt: async () => EMPTY_WORD, + call: async () => EMPTY_WORD, + ...overrides, + } +} + +describe('RpcContractInspector', () => { + it('reports an EOA without probing further', async () => { + const getStorageAt = vi.fn(async () => EMPTY_WORD) + const inspector = new RpcContractInspector({ readers: { base: reader({ getCode: async () => '0x', getStorageAt }) } }) + expect(await inspector.inspect(ADDR, 'base')).toEqual({ isContract: false, proxy: false }) + expect(getStorageAt).not.toHaveBeenCalled() + }) + + it('detects an EIP-1967 proxy and its implementation', async () => { + const inspector = new RpcContractInspector({ + readers: { + base: reader({ + getStorageAt: async (_a, slot) => (slot === SLOT_IMPLEMENTATION ? word(IMPL) : EMPTY_WORD), + }), + }, + }) + const facts = await inspector.inspect(ADDR, 'base') + expect(facts.isContract).toBe(true) + expect(facts.proxy).toBe(true) + expect(facts.implementation).toBe(IMPL) + }) + + it('is not a proxy when the implementation slot is empty', async () => { + const inspector = new RpcContractInspector({ readers: { base: reader() } }) + const facts = await inspector.inspect(ADDR, 'base') + expect(facts.proxy).toBe(false) + expect(facts.implementation).toBeUndefined() + }) + + it('prefers the proxy admin slot over owner(), since it can swap the code out', async () => { + const inspector = new RpcContractInspector({ + readers: { + base: reader({ + getStorageAt: async (_a, slot) => (slot === SLOT_ADMIN ? word(ADMIN) : EMPTY_WORD), + call: async () => word(OWNER), + }), + }, + }) + expect((await inspector.inspect(ADDR, 'base')).controller).toBe(ADMIN) + }) + + it('falls back to owner() when no admin slot is set', async () => { + const inspector = new RpcContractInspector({ readers: { base: reader({ call: async () => word(OWNER) }) } }) + expect((await inspector.inspect(ADDR, 'base')).controller).toBe(OWNER) + }) + + it('has no controller when the contract exposes neither', async () => { + const inspector = new RpcContractInspector({ readers: { base: reader() } }) + expect((await inspector.inspect(ADDR, 'base')).controller).toBeUndefined() + }) + + it('tolerates a reverting owner()/admin() without failing the whole inspection', async () => { + const inspector = new RpcContractInspector({ + readers: { + base: reader({ + call: async () => { throw new Error('execution reverted') }, + getStorageAt: async (_a, slot) => (slot === SLOT_IMPLEMENTATION ? word(IMPL) : EMPTY_WORD), + }), + }, + }) + const facts = await inspector.inspect(ADDR, 'base') + expect(facts.proxy).toBe(true) // proxy detection still succeeded + expect(facts.controller).toBeUndefined() + }) + + it('caches within the TTL and re-reads after it', async () => { + let t = 1000 + const getCode = vi.fn(async () => '0x60006000') + const inspector = new RpcContractInspector({ + readers: { base: reader({ getCode }) }, + cacheTtlMs: 5000, + now: () => t, + }) + await inspector.inspect(ADDR, 'base') + await inspector.inspect(ADDR.toUpperCase(), 'base') // same address, different case + expect(getCode).toHaveBeenCalledTimes(1) + t = 6001 + await inspector.inspect(ADDR, 'base') + expect(getCode).toHaveBeenCalledTimes(2) + }) + + it('caches per chain, not per address', async () => { + const getCode = vi.fn(async () => '0x60006000') + const inspector = new RpcContractInspector({ readers: { base: reader({ getCode }), opt: reader({ getCode }) } }) + await inspector.inspect(ADDR, 'base') + await inspector.inspect(ADDR, 'opt') + expect(getCode).toHaveBeenCalledTimes(2) + }) + + it('evicts the oldest entry past maxCacheEntries', async () => { + const getCode = vi.fn(async () => '0x60006000') + const inspector = new RpcContractInspector({ readers: { base: reader({ getCode }) }, maxCacheEntries: 2 }) + await inspector.inspect('0x' + '1'.repeat(40), 'base') + await inspector.inspect('0x' + '2'.repeat(40), 'base') + await inspector.inspect('0x' + '3'.repeat(40), 'base') // evicts the first + expect(getCode).toHaveBeenCalledTimes(3) + await inspector.inspect('0x' + '1'.repeat(40), 'base') // must re-read + expect(getCode).toHaveBeenCalledTimes(4) + }) + + it('rejects rather than hangs when the RPC stalls', async () => { + const inspector = new RpcContractInspector({ + readers: { base: reader({ getCode: () => new Promise(() => {}) }) }, + timeoutMs: 10, + }) + await expect(inspector.inspect(ADDR, 'base')).rejects.toThrow(/timed out after 10ms/) + }) + + it('rejects for an unconfigured chain rather than reporting a clean EOA', async () => { + const inspector = new RpcContractInspector({ readers: { base: reader() } }) + await expect(inspector.inspect(ADDR, 'unknownChain')).rejects.toThrow(/no chain reader configured/) + }) + + it('does not cache a failed inspection', async () => { + let fail = true + const getCode = vi.fn(async () => { + if (fail) throw new Error('rpc down') + return '0x60006000' + }) + const inspector = new RpcContractInspector({ readers: { base: reader({ getCode }) } }) + await expect(inspector.inspect(ADDR, 'base')).rejects.toThrow(/rpc down/) + fail = false + expect((await inspector.inspect(ADDR, 'base')).isContract).toBe(true) + }) +}) diff --git a/worker/test/denylist-manager.spec.ts b/worker/test/denylist-manager.spec.ts index 10a1c4c..9fefbfb 100644 --- a/worker/test/denylist-manager.spec.ts +++ b/worker/test/denylist-manager.spec.ts @@ -1,15 +1,22 @@ import { describe, it, expect, vi } from 'vitest' import { DenylistManager } from '../runtime/denylist-manager' import { createMetrics } from '../runtime/metrics' -import { Denylist } from '../assess/store' +import { RiskStore } from '../assess/store' +import type { RiskStoreBuild } from '../assess/assess' +import type { DegradedMode } from '../runtime/denylist-manager' import pino from 'pino' const silent = pino({ level: 'silent' }) -function dlWith(...addrs: string[]): Denylist { - const dl = new Denylist() - for (const a of addrs) dl.add(a, 'ofac', 'test') - return dl +function dlWith(...addrs: string[]): RiskStoreBuild { + const store = new RiskStore() + for (const a of addrs) store.upsert({ subject: a, labels: ['sanctions'], source: 'ofac' }) + return { store, degraded: [] } +} + +/** A clean build plus the list of tolerated sources that failed. */ +function degradedWith(degraded: string[], ...addrs: string[]): RiskStoreBuild { + return { ...dlWith(...addrs), degraded } } /** A controllable clock: read via now(), advance via tick(). */ @@ -19,10 +26,11 @@ function fakeClock(start = 1_000_000) { } function make(opts: { - build: () => Promise + build: () => Promise now: () => number refreshMs?: number maxStalenessMs?: number + degradedMode?: DegradedMode }) { const metrics = createMetrics() const mgr = new DenylistManager({ @@ -31,12 +39,76 @@ function make(opts: { sleep: async () => {}, refreshMs: opts.refreshMs ?? 60_000, maxStalenessMs: opts.maxStalenessMs ?? 120_000, + degradedMode: opts.degradedMode, logger: silent, metrics, }) return { mgr, metrics } } +/** + * The feed refresh exists so a newly published graph label is usable in seconds. A full rebuild + * re-downloads OFAC and OpenSanctions, so it cannot run on that cadence. + */ +describe('DenylistManager feed refresh', () => { + it('ingests the feed into the store already in use', async () => { + const store = new RiskStore() + const refreshFeed = vi.fn(async (s: RiskStore) => { + s.upsert({ subject: '0x' + 'a'.repeat(40), labels: ['sanctions_1hop'], source: 'trusted_indexer' }) + return 1 + }) + const m = new DenylistManager({ + build: async () => ({ store, degraded: [] }), + refreshFeed, + refreshMs: 60_000, + maxStalenessMs: 120_000, + logger: silent, + metrics: createMetrics(), + }) + await m.start() + expect(await m.refreshFeed()).toBe(1) + expect(refreshFeed).toHaveBeenCalledWith(store) + expect(store.has('0x' + 'a'.repeat(40))).toBe(true) + m.stop() + }) + + // Freshness is about the authoritative sources; a cheap feed fetch must not make a stale + // sanctions list look current. + it('does not reset the staleness clock', async () => { + let t = 1_000_000 + const m = new DenylistManager({ + build: async () => ({ store: new RiskStore(), degraded: [] }), + refreshFeed: async () => 1, + refreshMs: 60_000, + maxStalenessMs: 10_000, + now: () => t, + logger: silent, + metrics: createMetrics(), + }) + await m.start() + expect(m.state).toBe('READY') + t += 20_000 + await m.refreshFeed() + expect(m.evaluate()).toBe('HALTED') // still stale despite the feed refresh + m.stop() + }) + + it('survives a failing feed refresh without disturbing the store', async () => { + const m = new DenylistManager({ + build: async () => ({ store: new RiskStore(), degraded: [] }), + refreshFeed: async () => { throw new Error('indexer down') }, + refreshMs: 60_000, + maxStalenessMs: 120_000, + logger: silent, + metrics: createMetrics(), + }) + await m.start() + expect(await m.refreshFeed()).toBe(0) + expect(m.state).toBe('READY') + m.stop() + }) +}) + describe('DenylistManager', () => { it('starts INITIALIZING and becomes READY after a successful build', async () => { const clock = fakeClock() @@ -44,7 +116,7 @@ describe('DenylistManager', () => { expect(mgr.state).toBe('INITIALIZING') await mgr.start() expect(mgr.state).toBe('READY') - expect(mgr.assessor()('0x' + 'a'.repeat(40)).blocked).toBe(true) + expect((await mgr.assessor()('0x' + 'a'.repeat(40))).action).toBe('block') mgr.stop() }) @@ -108,9 +180,12 @@ describe('DenylistManager', () => { const ok = await mgr.refresh() expect(ok).toBe(false) expect(mgr.state).toBe('READY') // last good list still valid - expect(mgr.assessor()('0x' + 'e'.repeat(40)).blocked).toBe(true) + expect((await mgr.assessor()('0x' + 'e'.repeat(40))).action).toBe('block') const text = await metrics.registry.metrics() expect(text).toMatch(/dvn_denylist_refresh_total\{[^}]*result="failure"[^}]*\} 1/) + // Both outcomes are counted, including the initial build — a dashboard plotting the two + // together must not show failures only. + expect(text).toMatch(/dvn_denylist_refresh_total\{[^}]*result="success"[^}]*\} 1/) mgr.stop() }) @@ -146,6 +221,75 @@ describe('DenylistManager', () => { mgr.stop() }) + it('stays READY when a tolerated source is missing (degrade, the default)', async () => { + const clock = fakeClock() + const { mgr, metrics } = make({ + build: async () => degradedWith(['trusted_indexer'], '0x' + 'a'.repeat(40)), + now: clock.now, + }) + await mgr.start() + expect(mgr.state).toBe('READY') + expect(mgr.degraded).toEqual(['trusted_indexer']) + // Sanctions screening keeps working — losing graph labels must not cost us the OFAC list. + expect((await mgr.assessor()('0x' + 'a'.repeat(40))).action).toBe('block') + const text = await metrics.registry.metrics() + expect(text).toMatch(/dvn_source_degraded\{[^}]*source="trusted_indexer"[^}]*\} 1/) + mgr.stop() + }) + + it('HALTS on a degraded source when configured to halt', async () => { + const clock = fakeClock() + const { mgr, metrics } = make({ + build: async () => degradedWith(['trusted_indexer'], '0x' + 'a'.repeat(40)), + now: clock.now, + degradedMode: 'halt', + }) + await mgr.start() + expect(mgr.state).toBe('HALTED') + expect(() => mgr.assessor()).toThrowError(/not ready/i) + const text = await metrics.registry.metrics() + expect(text).toMatch(/dvn_halted\{[^}]*reason="degraded_source"[^}]*\} 1/) + mgr.stop() + }) + + it('recovers from a degraded halt once the source returns', async () => { + const clock = fakeClock() + let degraded = ['trusted_indexer'] + const { mgr, metrics } = make({ + build: async () => degradedWith(degraded, '0x' + 'a'.repeat(40)), + now: clock.now, + degradedMode: 'halt', + }) + await mgr.start() + expect(mgr.state).toBe('HALTED') + degraded = [] + expect(await mgr.refresh()).toBe(true) + expect(mgr.state).toBe('READY') + const text = await metrics.registry.metrics() + expect(text).toMatch(/dvn_halted\{[^}]*reason="degraded_source"[^}]*\} 0/) + expect(text).toMatch(/dvn_source_degraded\{[^}]*source="trusted_indexer"[^}]*\} 0/) + mgr.stop() + }) + + // Staleness outranks degradation: an aged store is unsafe no matter which sources built it. + it('reports stale_denylist rather than degraded_source when both apply', async () => { + const clock = fakeClock() + const { mgr, metrics } = make({ + build: async () => degradedWith(['trusted_indexer'], '0x' + 'a'.repeat(40)), + now: clock.now, + maxStalenessMs: 120_000, + degradedMode: 'halt', + }) + await mgr.start() + clock.tick(120_001) + expect(mgr.evaluate()).toBe('HALTED') + const text = await metrics.registry.metrics() + expect(text).toMatch(/dvn_halted\{[^}]*reason="stale_denylist"[^}]*\} 1/) + // Exactly one reason is ever asserted, so an alert cannot fire on a cleared cause. + expect(text).toMatch(/dvn_halted\{[^}]*reason="degraded_source"[^}]*\} 0/) + mgr.stop() + }) + it('throws from assessor() before the first successful build', () => { const clock = fakeClock() const { mgr } = make({ build: async () => dlWith(), now: clock.now }) diff --git a/worker/test/errors.spec.ts b/worker/test/errors.spec.ts new file mode 100644 index 0000000..d560428 --- /dev/null +++ b/worker/test/errors.spec.ts @@ -0,0 +1,76 @@ +import { describe, it, expect } from 'vitest' +import { briefError } from '../runtime/errors' + +/** + * The shapes ethers actually produced against OP Sepolia, trimmed to the fields the formatter + * reads. Both filled hundreds of characters of log line with the transaction and receipt. + */ +const REVERTING_ESTIMATE = Object.assign( + new Error( + 'cannot estimate gas; transaction may fail or may require manual gas limit ' + + '[ See: https://links.ethers.org/v5-errors-UNPREDICTABLE_GAS_LIMIT ] (error={"reason":"execution reverted",' + + '"code":"UNPREDICTABLE_GAS_LIMIT","transaction":{"data":"0x0894edf1…very long…"}}, tx={…})', + ), + { + code: 'UNPREDICTABLE_GAS_LIMIT', + reason: 'execution reverted', + error: { code: 'SERVER_ERROR', error: { code: 3, data: '0x4c3118d4' } }, + }, +) + +const MINED_REVERT = Object.assign( + new Error( + 'transaction failed [ See: https://links.ethers.org/v5-errors-CALL_EXCEPTION ] ' + + '(transactionHash="0x12ae45e1bb541e2770911046e940f72e03ba95355bb483baf4c0500345a73aed", ' + + 'transaction={…}, receipt={…}, code=CALL_EXCEPTION, version=providers/5.8.0)', + ), + { + code: 'CALL_EXCEPTION', + transactionHash: '0x12ae45e1bb541e2770911046e940f72e03ba95355bb483baf4c0500345a73aed', + receipt: { status: 0 }, + }, +) + +describe('briefError', () => { + it('names the custom error instead of dumping the transaction', () => { + const out = briefError(REVERTING_ESTIMATE) + expect(out).toContain('UNPREDICTABLE_GAS_LIMIT') + expect(out).toContain('LZ_ULN_Verifying') + expect(out).not.toContain('0x0894edf1') // no calldata + expect(out.length).toBeLessThan(120) + // Shorter than the message it replaces — and the real one, with the full transaction inlined, + // ran past a thousand characters where this fixture is abridged. + expect(out.length).toBeLessThan(REVERTING_ESTIMATE.message.length / 2) + }) + + it('keeps the transaction hash for a revert that was mined', () => { + const out = briefError(MINED_REVERT) + expect(out).toContain('CALL_EXCEPTION') + expect(out).toContain('tx 0x12ae45e1bb541e2770911046e940f72e03ba95355bb483baf4c0500345a73aed') + expect(out).not.toContain('receipt') + expect(out.length).toBeLessThan(140) + }) + + // Revert output and calldata both arrive under `data`; only one of them is an explanation. + it('does not mistake calldata for revert output', () => { + const noRevert = Object.assign(new Error('server error'), { + code: 'SERVER_ERROR', + data: '0x0894edf1' + 'ab'.repeat(200), + }) + expect(briefError(noRevert)).not.toContain('revert') + }) + + it('leaves ordinary errors as they were', () => { + expect(briefError(new Error('rpc down'))).toBe('rpc down') + expect(briefError('plain string')).toBe('plain string') + expect(briefError(undefined)).toBe('undefined') + }) + + it('reports an unmapped selector as its raw value rather than hiding it', () => { + const unknown = Object.assign(new Error('cannot estimate gas'), { + code: 'UNPREDICTABLE_GAS_LIMIT', + error: { error: { code: 3, data: '0xdeadbeef00000000' } }, + }) + expect(briefError(unknown)).toContain('revert 0xdeadbeef') + }) +}) diff --git a/worker/test/events.spec.ts b/worker/test/events.spec.ts index 92c41c4..2a2a5b8 100644 --- a/worker/test/events.spec.ts +++ b/worker/test/events.spec.ts @@ -1,5 +1,58 @@ -import { describe, it, expect } from 'vitest' -import { parseEncodedPacket } from '../chain/events' +import { describe, it, expect, vi } from 'vitest' +import { ethers } from 'ethers' +import { ENDPOINT_ABI, parseEncodedPacket, scanPacketSent } from '../chain/events' + +const HEADER = + '01' + '0000000000000007' + '00009d28' + + '000000000000000000000000' + 'aa'.repeat(20) + '00009d35' + + '000000000000000000000000' + 'bb'.repeat(20) +const GUID = '11'.repeat(32) +const OFT_MESSAGE = '000000000000000000000000' + 'cc'.repeat(20) + '0000000000000064' + +/** A provider that returns exactly the PacketSent logs we hand it. */ +function providerWith(encodedPayloads: string[]): ethers.providers.Provider { + const iface = new ethers.utils.Interface(ENDPOINT_ABI) + const logs = encodedPayloads.map((encodedPayload) => { + const { data, topics } = iface.encodeEventLog(iface.getEvent('PacketSent'), [ + encodedPayload, + '0x', + ethers.constants.AddressZero, + ]) + return { data, topics } + }) + return { getLogs: async () => logs } as unknown as ethers.providers.Provider +} + +describe('scanPacketSent', () => { + // The endpoint is shared by every OApp on the chain. Before this was handled, one stranger's + // 16-byte message threw out of the scan, and because the checkpoint freezes on failure the + // worker retried the same block forever and never advanced again. + it('skips foreign packets it cannot decode instead of aborting the scan', async () => { + const ours = '0x' + HEADER + GUID + OFT_MESSAGE + const foreignShortMessage = '0x' + HEADER + GUID + 'dd'.repeat(16) // real shape seen on OP Sepolia + const foreignTruncatedHeader = '0x' + 'ee'.repeat(40) + const onSkip = vi.fn() + + const packets = await scanPacketSent( + providerWith([foreignShortMessage, ours, foreignTruncatedHeader]), + '0xendpoint', + 1, + 2, + onSkip, + ) + + expect(packets).toHaveLength(1) + expect(packets[0].oft.toAddress).toBe('0x' + 'cc'.repeat(20)) + expect(onSkip).toHaveBeenCalledTimes(2) + // Skips stay identifiable: the payload hash needs only the split, not a successful decode. + for (const [payloadHash] of onSkip.mock.calls) expect(payloadHash).toMatch(/^0x[0-9a-f]{64}$/) + }) + + it('does not require an onSkip callback', async () => { + const packets = await scanPacketSent(providerWith(['0x' + HEADER + GUID + 'dd'.repeat(16)]), '0xe', 1, 2) + expect(packets).toEqual([]) + }) +}) describe('parseEncodedPacket', () => { it('splits header / guid / message and computes payloadHash', () => { diff --git a/worker/test/feed.spec.ts b/worker/test/feed.spec.ts new file mode 100644 index 0000000..62a0dcd --- /dev/null +++ b/worker/test/feed.spec.ts @@ -0,0 +1,297 @@ +import { describe, it, expect, vi } from 'vitest' +import { ethers } from 'ethers' +import { + ingestFeed, + parseFeed, + canonicalize, + signingPayload, + verifyFeedSigner, + FeedError, + type FeedVersionStore, +} from '../assess/ingest/feed' +import { RiskStore } from '../assess/store' +import { makeAssessor } from '../assess/assess' +import { POLICY_VERSION } from '../assess/policy' + +const SIGNER = new ethers.Wallet('0x' + '1'.repeat(64)) +const OTHER = new ethers.Wallet('0x' + '2'.repeat(64)) +const ADDR = '0x' + 'a'.repeat(40) +const NOW_MS = 1_800_000_000_000 +const NOW_SEC = Math.floor(NOW_MS / 1000) + +/** An in-memory version store, matching what the Checkpoint provides in production. */ +function versionStore(initial: Record = {}): FeedVersionStore { + const map = { ...initial } + return { get: (s) => map[s] ?? 0, set: (s, v) => void (map[s] = v) } +} + +type FeedBody = Record + +function feedBody(overrides: FeedBody = {}): FeedBody { + return { + version: 1, + generatedAt: NOW_SEC - 60, + expiresAt: NOW_SEC + 3600, + source: 'trusted-indexer-a', + policyVersion: POLICY_VERSION, + entries: [{ address: ADDR, labels: ['sanctions_1hop'], score: 82 }], + ...overrides, + } +} + +/** Sign a body the way the indexer will, and return the deliverable document. */ +async function signed(body: FeedBody = feedBody(), wallet = SIGNER): Promise { + const signature = await wallet.signMessage(signingPayload(body)) + return JSON.stringify({ ...body, signature }) +} + +function deps(overrides: Partial[2]> = {}) { + return { + now: () => NOW_MS, + versions: versionStore(), + ...overrides, + } as Parameters[2] +} + +const cfg = { url: 'https://indexer.test/feed.json', signers: [SIGNER.address] } + +describe('canonicalize', () => { + it('sorts object keys so both sides agree on the bytes', () => { + expect(canonicalize({ b: 1, a: 2 })).toBe('{"a":2,"b":1}') + expect(canonicalize({ a: 2, b: 1 })).toBe(canonicalize({ b: 1, a: 2 })) + }) + + it('preserves array order, which is semantic', () => { + expect(canonicalize([3, 1, 2])).toBe('[3,1,2]') + }) + + it('sorts nested keys too', () => { + expect(canonicalize({ z: { y: 1, x: 2 } })).toBe('{"z":{"x":2,"y":1}}') + }) + + // Float formatting is not guaranteed to round-trip across languages, so it is refused + // outright rather than risk a signature that verifies on one side only. + it('rejects non-integer numbers', () => { + expect(() => canonicalize({ confidence: 0.8 })).toThrow(/non-integer/) + expect(() => canonicalize([1, 2.5])).toThrow(/non-integer/) + }) + + it('excludes the signature from what gets signed', () => { + const body = feedBody() + expect(signingPayload({ ...body, signature: '0xdead' })).toBe(canonicalize(body)) + }) +}) + +describe('parseFeed', () => { + it('rejects non-JSON and non-objects', () => { + expect(() => parseFeed('not json')).toThrow(FeedError) + expect(() => parseFeed('[]')).toThrow(/must be a JSON object/) + }) + + it('rejects a malformed signature', async () => { + expect(() => parseFeed(JSON.stringify({ ...feedBody(), signature: '0x1234' }))).toThrow(/65-byte hex/) + }) + + it('rejects a bad address, empty labels, or an out-of-range score', async () => { + const bad = (entries: unknown) => () => parseFeed(JSON.stringify({ ...feedBody({ entries }), signature: '0x' + '1'.repeat(130) })) + expect(bad([{ address: '0xnope', labels: ['x'] }])).toThrow(/EVM address/) + expect(bad([{ address: ADDR, labels: [] }])).toThrow(/non-empty array/) + expect(bad([{ address: ADDR, labels: ['x'], score: 101 }])).toThrow(/<= 100/) + expect(bad([{ address: ADDR, labels: ['x'], score: 1.5 }])).toThrow(/integer/) + }) + + it('keeps unknown fields so the signature still covers them', async () => { + const body = feedBody({ someFutureField: 'kept' }) + const { raw } = parseFeed(await signed(body)) + expect(raw.someFutureField).toBe('kept') + }) + + it('lowercases a checksummed entry address', async () => { + const checksummed = '0x' + 'A'.repeat(40) // mixed case is what EIP-55 produces + const { feed } = parseFeed(await signed(feedBody({ entries: [{ address: checksummed, labels: ['x'] }] }))) + expect(feed.entries[0].address).toBe('0x' + 'a'.repeat(40)) + }) +}) + +describe('verifyFeedSigner', () => { + it('recovers the signer', async () => { + const body = feedBody() + const signature = await SIGNER.signMessage(signingPayload(body)) + expect(verifyFeedSigner(body, signature, [SIGNER.address])).toBe(SIGNER.address.toLowerCase()) + }) + + it('rejects a correct signature from a key that is not allowlisted', async () => { + const body = feedBody() + const signature = await OTHER.signMessage(signingPayload(body)) + expect(() => verifyFeedSigner(body, signature, [SIGNER.address])).toThrow(/not in the signer allowlist/) + }) + + it('rejects a tampered body', async () => { + const body = feedBody() + const signature = await SIGNER.signMessage(signingPayload(body)) + const tampered = { ...body, entries: [{ address: ADDR, labels: ['sanctions'] }] } + expect(() => verifyFeedSigner(tampered, signature, [SIGNER.address])).toThrow(/not in the signer allowlist/) + }) +}) + +describe('ingestFeed', () => { + it('applies a valid feed as trusted_indexer entries with the feed TTL', async () => { + const store = new RiskStore({ now: () => NOW_MS }) + const body = feedBody() + const n = await ingestFeed(store, cfg, deps({ fetcher: async () => await signed(body) })) + expect(n).toBe(1) + const entry = store.lookup(ADDR)[0] + expect(entry.source).toBe('trusted_indexer') + expect(entry.labels).toEqual(['sanctions_1hop']) + expect(entry.score).toBe(82) + expect(entry.confidence).toBe(0.8) // from SOURCE_TRUST, not asserted by the feed + expect(entry.expiresAt).toBe((body.expiresAt as number) * 1000) + }) + + it('feeds straight into a manual-review verdict', async () => { + const store = new RiskStore({ now: () => NOW_MS }) + await ingestFeed(store, cfg, deps({ fetcher: async () => await signed() })) + const r = await makeAssessor(store)(ADDR) + expect(r.score).toBe(82) // asserted score beats the label weight of 70 + expect(r.action).toBe('manual-review') + }) + + // An asserted score describes the subject, so it counts once per claim. Applying it per label + // multiplied it by the label count and turned a review into a block. + it('counts an asserted score once, not once per label', async () => { + const store = new RiskStore({ now: () => NOW_MS }) + const body = feedBody({ + entries: [{ address: ADDR, labels: ['unverified_contract', 'upgradeable_proxy'], score: 82 }], + }) + await ingestFeed(store, cfg, deps({ fetcher: async () => await signed(body) })) + const r = await makeAssessor(store)(ADDR) + // max(20 + 15, 82) = 82, NOT 82 + 82. + expect(r.score).toBe(82) + expect(r.action).toBe('manual-review') + }) + + it('lets the label sum win when it exceeds the asserted score', async () => { + const store = new RiskStore({ now: () => NOW_MS }) + const body = feedBody({ entries: [{ address: ADDR, labels: ['sanctions_1hop'], score: 10 }] }) + await ingestFeed(store, cfg, deps({ fetcher: async () => await signed(body) })) + // A source cannot talk its own labels down: max(70, 10) = 70. + expect((await makeAssessor(store)(ADDR)).score).toBe(70) + }) + + // A feed may carry its own `action`; honouring it would move enforcement authority to the + // indexer, which is exactly what SOURCE_TRUST exists to prevent. + it('ignores a feed-supplied action', async () => { + const store = new RiskStore({ now: () => NOW_MS }) + const body = feedBody({ entries: [{ address: ADDR, labels: ['unverified_contract'], action: 'block' }] }) + await ingestFeed(store, cfg, deps({ fetcher: async () => await signed(body) })) + const r = await makeAssessor(store)(ADDR) + expect(r.score).toBe(20) + expect(r.action).toBe('allow') // our policy decides, not the feed + }) + + it('rejects an unsigned or wrongly signed feed', async () => { + const store = new RiskStore({ now: () => NOW_MS }) + await expect( + ingestFeed(store, cfg, deps({ fetcher: async () => await signed(feedBody(), OTHER) })), + ).rejects.toThrow(/untrusted_signer/) + expect(store.size).toBe(0) + }) + + it('rejects a policyVersion mismatch', async () => { + const store = new RiskStore({ now: () => NOW_MS }) + const body = feedBody({ policyVersion: POLICY_VERSION + 1 }) + await expect(ingestFeed(store, cfg, deps({ fetcher: async () => await signed(body) }))).rejects.toThrow( + /policy_mismatch/, + ) + expect(store.size).toBe(0) + }) + + it('rejects a rollback to an older version', async () => { + const store = new RiskStore({ now: () => NOW_MS }) + const versions = versionStore({ 'trusted-indexer-a': 5 }) + await expect( + ingestFeed(store, cfg, deps({ fetcher: async () => await signed(feedBody({ version: 4 })), versions })), + ).rejects.toThrow(/replayed/) + expect(store.size).toBe(0) + expect(versions.get('trusted-indexer-a')).toBe(5) + + await ingestFeed(store, cfg, deps({ fetcher: async () => await signed(feedBody({ version: 6 })), versions })) + expect(versions.get('trusted-indexer-a')).toBe(6) + }) + + // A fresh store on a rebuild or a restart holds the version but not the labels, so refusing the + // current version would leave it with no feed-derived labels at all until the indexer published. + it('re-applies the version already accepted, so a fresh store recovers its labels', async () => { + const store = new RiskStore({ now: () => NOW_MS }) + const versions = versionStore({ 'trusted-indexer-a': 5 }) + const applied = await ingestFeed( + store, + cfg, + deps({ fetcher: async () => await signed(feedBody({ version: 5 })), versions }), + ) + expect(applied).toBeGreaterThan(0) + expect(store.size).toBeGreaterThan(0) + expect(versions.get('trusted-indexer-a')).toBe(5) + }) + + it('tracks versions per source, so two indexers do not block each other', async () => { + const store = new RiskStore({ now: () => NOW_MS }) + const versions = versionStore({ 'indexer-a': 9 }) + await ingestFeed( + store, + cfg, + deps({ fetcher: async () => await signed(feedBody({ source: 'indexer-b', version: 1 })), versions }), + ) + expect(versions.get('indexer-b')).toBe(1) + expect(versions.get('indexer-a')).toBe(9) + }) + + it('rejects an already-expired feed', async () => { + const store = new RiskStore({ now: () => NOW_MS }) + const body = feedBody({ expiresAt: NOW_SEC - 1 }) + await expect(ingestFeed(store, cfg, deps({ fetcher: async () => await signed(body) }))).rejects.toThrow(/expired/) + }) + + it('rejects a future-dated feed beyond the skew allowance', async () => { + const store = new RiskStore({ now: () => NOW_MS }) + const body = feedBody({ generatedAt: NOW_SEC + 3000 }) + await expect( + ingestFeed(store, { ...cfg, maxSkewSec: 300 }, deps({ fetcher: async () => await signed(body) })), + ).rejects.toThrow(/future_dated/) + }) + + it('tolerates clock skew inside the allowance', async () => { + const store = new RiskStore({ now: () => NOW_MS }) + const body = feedBody({ generatedAt: NOW_SEC + 100 }) + await expect( + ingestFeed(store, { ...cfg, maxSkewSec: 300 }, deps({ fetcher: async () => await signed(body) })), + ).resolves.toBe(1) + }) + + it('reports a fetch failure as fetch_failed', async () => { + const store = new RiskStore({ now: () => NOW_MS }) + await expect( + ingestFeed(store, cfg, deps({ fetcher: async () => { throw new Error('502 bad gateway') } })), + ).rejects.toThrow(/fetch_failed/) + }) + + it('does not bump the accepted version when the feed is rejected', async () => { + const store = new RiskStore({ now: () => NOW_MS }) + const versions = versionStore() + const set = vi.spyOn(versions, 'set') + await expect( + ingestFeed(store, cfg, deps({ fetcher: async () => await signed(feedBody(), OTHER), versions })), + ).rejects.toThrow() + expect(set).not.toHaveBeenCalled() + }) + + it('lets feed labels expire on their own once the feed goes stale', async () => { + let t = NOW_MS + const store = new RiskStore({ now: () => t }) + const body = feedBody({ expiresAt: NOW_SEC + 60 }) + await ingestFeed(store, cfg, deps({ fetcher: async () => await signed(body), now: () => t })) + expect((await makeAssessor(store)(ADDR)).action).toBe('manual-review') + t = NOW_MS + 61_000 + expect((await makeAssessor(store)(ADDR)).action).toBe('allow') + }) +}) diff --git a/worker/test/http.spec.ts b/worker/test/http.spec.ts index 5828ea4..f0194a2 100644 --- a/worker/test/http.spec.ts +++ b/worker/test/http.spec.ts @@ -11,10 +11,10 @@ afterEach(async () => { handle = undefined }) -async function start(isReady: () => boolean) { +async function start(isReady: () => boolean, extra: { pending?: () => unknown; status?: () => unknown } = {}) { const metrics = createMetrics() metrics.up.set(1) - handle = await startHttpServer({ port: 0, metrics, isReady, logger: silent }) + handle = await startHttpServer({ port: 0, metrics, isReady, logger: silent, ...extra }) return `http://127.0.0.1:${handle.port}` } @@ -46,4 +46,28 @@ describe('startHttpServer', () => { const base = await start(() => true) expect((await fetch(`${base}/nope`)).status).toBe(404) }) + + it('serves /pending and /status as JSON when providers are wired', async () => { + const base = await start(() => true, { + pending: () => ({ pending: [{ payloadHash: '0xabc', action: 'manual-review' }] }), + status: () => ({ state: 'READY', degraded: [] }), + }) + const pending = await fetch(`${base}/pending`) + expect(pending.headers.get('content-type')).toMatch(/json/) + expect(await pending.json()).toEqual({ pending: [{ payloadHash: '0xabc', action: 'manual-review' }] }) + expect(await (await fetch(`${base}/status`)).json()).toMatchObject({ state: 'READY' }) + }) + + it('hides /pending and /status when no provider is wired', async () => { + const base = await start(() => true) + expect((await fetch(`${base}/pending`)).status).toBe(404) + expect((await fetch(`${base}/status`)).status).toBe(404) + }) + + // The demo dashboard reads these endpoints straight from the browser. + it('sends a permissive CORS header on every response', async () => { + const base = await start(() => true) + expect((await fetch(`${base}/healthz`)).headers.get('access-control-allow-origin')).toBe('*') + expect((await fetch(`${base}/metrics`)).headers.get('access-control-allow-origin')).toBe('*') + }) }) diff --git a/worker/test/metrics.spec.ts b/worker/test/metrics.spec.ts index f282259..9f433ed 100644 --- a/worker/test/metrics.spec.ts +++ b/worker/test/metrics.spec.ts @@ -8,11 +8,20 @@ describe('createMetrics', () => { const m = createMetrics() m.up.set(1) m.ready.set(0) - m.vetoes.inc({ chain: 'baseSepolia', tag: 'ofac' }) + m.decisions.inc({ chain: 'baseSepolia', action: 'block' }) const text = await m.registry.metrics() expect(text).toMatch(/dvn_up\{service="compliance-dvn"\} 1/) expect(text).toMatch(/dvn_ready\{service="compliance-dvn"\} 0/) - expect(text).toMatch(/dvn_vetoes_total\{[^}]*chain="baseSepolia"[^}]*tag="ofac"[^}]*\} 1/) + expect(text).toMatch(/dvn_decisions_total\{[^}]*chain="baseSepolia"[^}]*action="block"[^}]*\} 1/) + }) + + it('tracks held packets and observed approvals', async () => { + const m = createMetrics() + m.pendingPackets.set({ action: 'manual-review' }, 2) + m.approvals.inc({ chain: 'optimismSepolia' }) + const text = await m.registry.metrics() + expect(text).toMatch(/dvn_pending_packets\{[^}]*action="manual-review"[^}]*\} 2/) + expect(text).toMatch(/dvn_approvals_total\{[^}]*chain="optimismSepolia"[^}]*\} 1/) }) it('tracks denylist gauges and refresh outcomes', async () => { diff --git a/worker/test/mixers.spec.ts b/worker/test/mixers.spec.ts index cd037b7..20a6ab2 100644 --- a/worker/test/mixers.spec.ts +++ b/worker/test/mixers.spec.ts @@ -1,20 +1,25 @@ import { describe, it, expect } from 'vitest' import { ingestMixers, MIXER_ADDRESSES } from '../assess/ingest/mixers' import { loadTestDenylist } from '../assess/testDenylist' -import { Denylist } from '../assess/store' +import { RiskStore } from '../assess/store' describe('mixers + test denylist', () => { - it('loads curated mixer addresses', () => { - const dl = new Denylist() - ingestMixers(dl) - expect(dl.size).toBe(MIXER_ADDRESSES.length) - expect(dl.lookup(MIXER_ADDRESSES[0])?.tags).toContain('mixer') + it('loads curated mixer addresses with ofac authority', () => { + const store = new RiskStore() + ingestMixers(store) + expect(store.size).toBe(MIXER_ADDRESSES.length) + const entry = store.lookup(MIXER_ADDRESSES[0])[0] + expect(entry.labels).toContain('sanctioned_mixer') + expect(entry.source).toBe('ofac') + expect(entry.subjectType).toBe('contract') }) it('loads operator test entries from env CSV', () => { - const dl = new Denylist() - loadTestDenylist(dl, '0xdeadbeef00000000000000000000000000000001,0xDEADBEEF00000000000000000000000000000002') - expect(dl.has('0xdeadbeef00000000000000000000000000000001')).toBe(true) - expect(dl.lookup('0xdeadbeef00000000000000000000000000000002')?.tags).toContain('test') + const store = new RiskStore() + loadTestDenylist(store, '0xdeadbeef00000000000000000000000000000001,0xDEADBEEF00000000000000000000000000000002') + expect(store.has('0xdeadbeef00000000000000000000000000000001')).toBe(true) + const entry = store.lookup('0xdeadbeef00000000000000000000000000000002')[0] + expect(entry.labels).toContain('operator_deny') + expect(entry.source).toBe('operator') }) }) diff --git a/worker/test/ofac.spec.ts b/worker/test/ofac.spec.ts index 5cdcc5d..152c77e 100644 --- a/worker/test/ofac.spec.ts +++ b/worker/test/ofac.spec.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest' import { parseOfacList, ingestOfac } from '../assess/ingest/ofac' -import { Denylist } from '../assess/store' +import { RiskStore } from '../assess/store' describe('OFAC ingest', () => { it('parses a JSON array of addresses', () => { @@ -8,10 +8,12 @@ describe('OFAC ingest', () => { expect(addrs).toEqual(['0xaaa', '0xbbb']) }) - it('loads parsed addresses into a denylist', async () => { - const dl = new Denylist() - await ingestOfac(dl, async () => JSON.stringify(['0x1234567890123456789012345678901234567890'])) - expect(dl.has('0x1234567890123456789012345678901234567890')).toBe(true) - expect(dl.lookup('0x1234567890123456789012345678901234567890')?.tags).toContain('ofac') + it('loads parsed addresses into the risk store as ofac sanctions entries', async () => { + const store = new RiskStore() + await ingestOfac(store, async () => JSON.stringify(['0x1234567890123456789012345678901234567890'])) + expect(store.has('0x1234567890123456789012345678901234567890')).toBe(true) + const entry = store.lookup('0x1234567890123456789012345678901234567890')[0] + expect(entry.labels).toContain('sanctions') + expect(entry.source).toBe('ofac') }) }) diff --git a/worker/test/scanner.spec.ts b/worker/test/scanner.spec.ts index 4c9acd1..8366273 100644 --- a/worker/test/scanner.spec.ts +++ b/worker/test/scanner.spec.ts @@ -1,9 +1,11 @@ import { describe, it, expect, vi } from 'vitest' -import { scanChainOnce, verifyPacket } from '../runtime/scanner' +import { scanChainOnce, verifyPacket, processDeferred } from '../runtime/scanner' import { createMetrics } from '../runtime/metrics' import { Checkpoint } from '../checkpoint' import { makeAssessor } from '../assess/assess' -import { Denylist } from '../assess/store' +import { RiskStore } from '../assess/store' +import { DELAY_POLICY } from '../assess/policy' +import { ACTION_CODES, decodeReasonMask } from '../assess/verdict' import type { ResolvedChain } from '../runtime/config' import type { ParsedPacket } from '../chain/events' import pino from 'pino' @@ -22,6 +24,10 @@ const optChain: ResolvedChain = { rpc: 'x', endpoint: '0xe', sendUln: '0xs', receiveUln: '0xr2', dvn: '0xd2', } +const PAYLOAD = '0x' + 'a'.repeat(64) +const KEY = `${PAYLOAD}:40232` +const SENDER = '0x' + '1'.repeat(40) + function tmpCheckpoint(): Checkpoint { const dir = mkdtempSync(join(tmpdir(), 'dvn-cp-')) return new Checkpoint(join(dir, 'cp.json')) @@ -30,15 +36,46 @@ function tmpCheckpoint(): Checkpoint { function packet(overrides: Partial = {}): ParsedPacket { return { encoded: '0x', header: '0xheader', guid: '0xguid', message: '0xmsg', - payloadHash: '0x' + 'a'.repeat(64), srcEid: 40245, dstEid: 40232, - senderAddress: '0x' + '1'.repeat(40), receiverAddress: '0x' + '2'.repeat(40), + payloadHash: PAYLOAD, srcEid: 40245, dstEid: 40232, + senderAddress: SENDER, receiverAddress: '0x' + '2'.repeat(40), oft: { toAddress: '0x' + '3'.repeat(40), amountSD: 1n, composed: false }, headerFields: {} as never, ...overrides, } } +/** A store whose only entry pushes SENDER to the given action. */ +function storeWith(labels: string[], source: 'ofac' | 'trusted_indexer' = 'ofac'): RiskStore { + const store = new RiskStore() + store.upsert({ subject: SENDER, labels, source }) + return store +} + +function deps(overrides: Record = {}) { + return { + assessor: makeAssessor(new RiskStore()), + resolveDst: (eid: number) => (eid === 40232 ? optChain : undefined), + verify: vi.fn(async () => '0xverifytx'), + commit: vi.fn(async () => '0xcommittx'), + execute: vi.fn(async () => '0xlzreceivetx'), + recordVerdict: vi.fn(async () => '0xrecordtx'), + emitVerdictFor: new Set(['block']), + checkpoint: tmpCheckpoint(), + metrics: createMetrics(), + logger: silent, + srcChainKey: 'baseSepolia', + ...overrides, + } as never as Parameters[1] & { + verify: ReturnType + commit: ReturnType + execute: ReturnType + recordVerdict: ReturnType + } +} + describe('scanChainOnce', () => { + const noScans = { scanAssigned: vi.fn(), scanPackets: vi.fn(), scanApproved: vi.fn() } + it('FREEZES checkpoints and skips scanning when not READY (fail-closed)', async () => { const cp = tmpCheckpoint() cp.setLastBlock('baseSepolia', 100) @@ -49,10 +86,12 @@ describe('scanChainOnce', () => { provider: { getBlockNumber: async () => 1000 }, confirmations: 5, scanWindow: 50, + scanChunk: 2000, state: () => 'HALTED', checkpoint: cp, scanAssigned: vi.fn(), scanPackets, + scanApproved: vi.fn(), handlePacket, metrics: createMetrics(), logger: silent, @@ -65,7 +104,6 @@ describe('scanChainOnce', () => { it('scans, handles only assigned packets, and advances the checkpoint when READY', async () => { const cp = tmpCheckpoint() cp.setLastBlock('baseSepolia', 100) - const assignedHash = '0x' + 'a'.repeat(64) const handlePacket = vi.fn(async () => {}) const metrics = createMetrics() await scanChainOnce({ @@ -73,10 +111,12 @@ describe('scanChainOnce', () => { provider: { getBlockNumber: async () => 1000 }, confirmations: 5, scanWindow: 50, + scanChunk: 2000, state: () => 'READY', checkpoint: cp, - scanAssigned: async () => new Set([assignedHash]), - scanPackets: async () => [packet({ payloadHash: assignedHash }), packet({ payloadHash: '0x' + 'b'.repeat(64) })], + scanAssigned: async () => new Set([PAYLOAD]), + scanPackets: async () => [packet(), packet({ payloadHash: '0x' + 'b'.repeat(64) })], + scanApproved: async () => new Set(), handlePacket, metrics, logger: silent, @@ -87,10 +127,33 @@ describe('scanChainOnce', () => { expect(text).toMatch(/dvn_checkpoint_block\{[^}]*chain="baseSepolia"[^}]*\} 995/) }) + it('records owner approvals seen in the window', async () => { + const cp = tmpCheckpoint() + cp.setLastBlock('baseSepolia', 100) + const metrics = createMetrics() + await scanChainOnce({ + chain: baseChain, + provider: { getBlockNumber: async () => 1000 }, + confirmations: 5, + scanWindow: 50, + scanChunk: 2000, + state: () => 'READY', + checkpoint: cp, + scanAssigned: async () => new Set(), + scanPackets: async () => [], + scanApproved: async () => new Set([PAYLOAD]), + handlePacket: vi.fn(), + metrics, + logger: silent, + }) + expect(cp.isApproved(PAYLOAD)).toBe(true) + const text = await metrics.registry.metrics() + expect(text).toMatch(/dvn_approvals_total\{[^}]*chain="baseSepolia"[^}]*\} 1/) + }) + it('aborts mid-scan and freezes the checkpoint if state flips to HALTED during the awaits (TOCTOU)', async () => { const cp = tmpCheckpoint() cp.setLastBlock('baseSepolia', 100) - const assignedHash = '0x' + 'a'.repeat(64) let state: 'READY' | 'HALTED' = 'READY' const handlePacket = vi.fn(async () => {}) await scanChainOnce({ @@ -98,19 +161,21 @@ describe('scanChainOnce', () => { provider: { getBlockNumber: async () => 1000 }, confirmations: 5, scanWindow: 50, + scanChunk: 2000, state: () => state, checkpoint: cp, - scanAssigned: async () => new Set([assignedHash]), - // Simulate the denylist aging into HALTED during the RPC await window. + scanAssigned: async () => new Set([PAYLOAD]), + // Simulate the risk store aging into HALTED during the RPC await window. scanPackets: async () => { state = 'HALTED' - return [packet({ payloadHash: assignedHash })] + return [packet()] }, + scanApproved: async () => new Set(), handlePacket, metrics: createMetrics(), logger: silent, }) - expect(handlePacket).not.toHaveBeenCalled() // never verified against the stale list + expect(handlePacket).not.toHaveBeenCalled() // never verified against the stale store expect(cp.getLastBlock('baseSepolia')).toBe(100) // checkpoint frozen, not advanced to 995 }) @@ -123,9 +188,10 @@ describe('scanChainOnce', () => { provider: { getBlockNumber: async () => 1000 }, confirmations: 5, scanWindow: 50, + scanChunk: 2000, state: () => 'READY', checkpoint: cp, - scanAssigned: vi.fn(), + ...noScans, scanPackets, handlePacket: vi.fn(), metrics: createMetrics(), @@ -134,8 +200,70 @@ describe('scanChainOnce', () => { expect(scanPackets).not.toHaveBeenCalled() }) - it('counts a scan error and rethrows for the caller to isolate', async () => { + // The fail-closed freeze holds the checkpoint through an outage, so on recovery the gap can be + // far wider than an RPC's getLogs cap. Without chunking every tick fails and the gap only grows. + it('walks a gap wider than the RPC range cap in bounded chunks', async () => { const cp = tmpCheckpoint() + cp.setLastBlock('baseSepolia', 1000) + const ranges: Array<[number, number]> = [] + const scanPackets = vi.fn(async (from: number, to: number) => { + if (to - from + 1 > 2000) throw new Error('query exceeds max block range 2000') + ranges.push([from, to]) + return [] + }) + await scanChainOnce({ + chain: baseChain, + provider: { getBlockNumber: async () => 6005 }, // safeHead 6000 => a 5000-block gap + confirmations: 5, + scanWindow: 50, + scanChunk: 2000, + state: () => 'READY', + checkpoint: cp, + scanAssigned: async () => new Set(), + scanPackets, + scanApproved: async () => new Set(), + handlePacket: vi.fn(), + metrics: createMetrics(), + logger: silent, + }) + expect(ranges).toEqual([ + [1001, 3000], + [3001, 5000], + [5001, 6000], + ]) + expect(cp.getLastBlock('baseSepolia')).toBe(6000) + }) + + // Per-chunk advance is what makes recovery possible: a failure mid-gap must keep the chunks + // already screened, or the worker restarts the whole gap every tick and never converges. + it('keeps the chunks it already screened when a later chunk fails', async () => { + const cp = tmpCheckpoint() + cp.setLastBlock('baseSepolia', 1000) + let calls = 0 + await expect( + scanChainOnce({ + chain: baseChain, + provider: { getBlockNumber: async () => 6005 }, + confirmations: 5, + scanWindow: 50, + scanChunk: 2000, + state: () => 'READY', + checkpoint: cp, + scanAssigned: async () => new Set(), + scanPackets: async () => { + if (++calls === 2) throw new Error('rpc down') + return [] + }, + scanApproved: async () => new Set(), + handlePacket: vi.fn(), + metrics: createMetrics(), + logger: silent, + }), + ).rejects.toThrow(/rpc down/) + expect(cp.getLastBlock('baseSepolia')).toBe(3000) // first chunk kept, not rewound to 1000 + }) + + it('counts a scan error and rethrows for the caller to isolate', async () => { const metrics = createMetrics() await expect( scanChainOnce({ @@ -143,10 +271,10 @@ describe('scanChainOnce', () => { provider: { getBlockNumber: async () => { throw new Error('rpc down') } }, confirmations: 5, scanWindow: 50, + scanChunk: 2000, state: () => 'READY', - checkpoint: cp, - scanAssigned: vi.fn(), - scanPackets: vi.fn(), + checkpoint: tmpCheckpoint(), + ...noScans, handlePacket: vi.fn(), metrics, logger: silent, @@ -158,87 +286,543 @@ describe('scanChainOnce', () => { }) describe('verifyPacket', () => { - const resolveDst = (eid: number) => (eid === 40232 ? optChain : undefined) + it('ALLOW: verifies and commits a clean packet, then marks it processed', async () => { + const d = deps() + await verifyPacket(packet(), d) + expect(d.verify).toHaveBeenCalledOnce() + expect(d.commit).toHaveBeenCalledOnce() + expect(d.checkpoint.isProcessed(KEY)).toBe(true) + }) - it('verifies and commits a clean packet, then marks it processed', async () => { - const cp = tmpCheckpoint() - const verify = vi.fn(async () => '0xverifytx') - const commit = vi.fn(async () => '0xcommittx') - await verifyPacket(packet(), { - assessor: makeAssessor(new Denylist()), - resolveDst, - verify, - commit, - checkpoint: cp, - metrics: createMetrics(), - logger: silent, - srcChainKey: 'baseSepolia', - }) - expect(verify).toHaveBeenCalledOnce() - expect(commit).toHaveBeenCalledOnce() - expect(cp.isProcessed('0x' + 'a'.repeat(64) + ':40232')).toBe(true) + it('BLOCK: vetoes a sanctioned packet, marks processed, counts the decision', async () => { + const d = deps({ assessor: makeAssessor(storeWith(['sanctions'])) }) + await verifyPacket(packet(), d) + expect(d.verify).not.toHaveBeenCalled() + expect(d.commit).not.toHaveBeenCalled() + expect(d.checkpoint.isProcessed(KEY)).toBe(true) + const text = await d.metrics.registry.metrics() + expect(text).toMatch(/dvn_decisions_total\{[^}]*action="block"[^}]*\} 1/) }) - it('VETOES a sanctioned packet: no verify, marks processed, increments veto metric', async () => { - const cp = tmpCheckpoint() - const dl = new Denylist() - dl.add('0x' + '1'.repeat(40), 'ofac', 'sanctioned sender') - const verify = vi.fn() - const commit = vi.fn() - const metrics = createMetrics() - await verifyPacket(packet(), { - assessor: makeAssessor(dl), - resolveDst, - verify, - commit, - checkpoint: cp, - metrics, - logger: silent, - srcChainKey: 'baseSepolia', - }) - expect(verify).not.toHaveBeenCalled() - expect(commit).not.toHaveBeenCalled() - expect(cp.isProcessed('0x' + 'a'.repeat(64) + ':40232')).toBe(true) - const text = await metrics.registry.metrics() - expect(text).toMatch(/dvn_vetoes_total\{[^}]*tag="ofac"[^}]*\} 1/) + it('MANUAL-REVIEW: withholds and holds indefinitely, awaiting approval', async () => { + const d = deps({ assessor: makeAssessor(storeWith(['sanctions_1hop'], 'trusted_indexer')) }) + await verifyPacket(packet(), d) + expect(d.verify).not.toHaveBeenCalled() + expect(d.checkpoint.isProcessed(KEY)).toBe(false) // NOT settled + const rec = d.checkpoint.getDeferred(KEY)! + expect(rec.action).toBe('manual-review') + expect(rec.score).toBe(70) + // Each party keeps its own chain: the sender is on the source, the rest on the destination. + expect(rec.parties).toEqual([ + { subject: SENDER, chainKey: 'baseSepolia' }, + { subject: '0x' + '2'.repeat(40), chainKey: 'optimismSepolia' }, + { subject: '0x' + '3'.repeat(40), chainKey: 'optimismSepolia' }, + ]) + expect(rec.retryAfter).toBe(Number.MAX_SAFE_INTEGER) // clock never releases it + const text = await d.metrics.registry.metrics() + expect(text).toMatch(/dvn_pending_packets\{[^}]*action="manual-review"[^}]*\} 1/) + }) + + it('DELAY: withholds with a clock-based retry', async () => { + const now = 1_000_000 + const d = deps({ assessor: makeAssessor(storeWith(['contract_admin_risk'], 'trusted_indexer')), now: () => now }) + await verifyPacket(packet(), d) + expect(d.verify).not.toHaveBeenCalled() + const rec = d.checkpoint.getDeferred(KEY)! + expect(rec.action).toBe('delay') + expect(rec.retryAfter).toBe(now + DELAY_POLICY.retryAfterMs) + expect(rec.attempts).toBe(0) }) it('skips a packet already processed', async () => { - const cp = tmpCheckpoint() - cp.markProcessed('0x' + 'a'.repeat(64) + ':40232') - const verify = vi.fn() - await verifyPacket(packet(), { - assessor: makeAssessor(new Denylist()), - resolveDst, verify, commit: vi.fn(), - checkpoint: cp, metrics: createMetrics(), logger: silent, srcChainKey: 'baseSepolia', - }) - expect(verify).not.toHaveBeenCalled() + const d = deps() + d.checkpoint.markProcessed(KEY) + await verifyPacket(packet(), d) + expect(d.verify).not.toHaveBeenCalled() + }) + + it('skips a packet already held — the deferred queue owns it', async () => { + const d = deps({ assessor: makeAssessor(storeWith(['sanctions_1hop'], 'trusted_indexer')) }) + await verifyPacket(packet(), d) + d.verify.mockClear() + await verifyPacket(packet(), d) // second sighting of the same packet + expect(d.verify).not.toHaveBeenCalled() + expect(d.checkpoint.deferredEntries().length).toBe(1) }) it('skips a packet whose destination EID is unknown', async () => { - const cp = tmpCheckpoint() - const verify = vi.fn() - await verifyPacket(packet({ dstEid: 99999 }), { - assessor: makeAssessor(new Denylist()), - resolveDst, verify, commit: vi.fn(), - checkpoint: cp, metrics: createMetrics(), logger: silent, srcChainKey: 'baseSepolia', + const d = deps() + await verifyPacket(packet({ dstEid: 99999 }), d) + expect(d.verify).not.toHaveBeenCalled() + }) + + /** + * Committing only makes a packet executable. No executor runs `lzReceive` for a custom DVN + * pathway, so without this the message would sit committed and undelivered. + */ + it('ALLOW: drives lzReceive after the commit lands', async () => { + const d = deps() + await verifyPacket(packet(), d) + expect(d.execute).toHaveBeenCalledOnce() + const [dst, header, guid, message] = d.execute.mock.calls[0] + expect(dst.key).toBe('optimismSepolia') + expect(header).toBe('0xheader') + expect(guid).toBe('0xguid') + expect(message).toBe('0xmsg') + const text = await d.metrics.registry.metrics() + expect(text).toMatch(/dvn_deliveries_total\{[^}]*result="success"[^}]*\} 1/) + }) + + it('does not attempt delivery when the commit failed', async () => { + const d = deps({ commit: vi.fn(async () => { throw new Error('not committable yet') }) }) + await verifyPacket(packet(), d) + expect(d.execute).not.toHaveBeenCalled() + }) + + // Enforcement is already settled by then, so a delivery failure must not undo the verification. + it('keeps the packet processed when lzReceive fails', async () => { + const d = deps({ execute: vi.fn(async () => { throw new Error('already executed') }) }) + await verifyPacket(packet(), d) + expect(d.checkpoint.isProcessed(KEY)).toBe(true) + const text = await d.metrics.registry.metrics() + expect(text).toMatch(/dvn_deliveries_total\{[^}]*result="failure"[^}]*\} 1/) + }) + + it('tolerates a packet with no guid — nothing to deliver with', async () => { + const d = deps() + await verifyPacket(packet({ guid: undefined }), d) + expect(d.verify).toHaveBeenCalledOnce() + expect(d.execute).not.toHaveBeenCalled() + }) + + it('carries guid and message through a hold, so a released packet still delivers', async () => { + const now = 1_000_000 + const d = deps({ assessor: makeAssessor(storeWith(['sanctions_1hop'], 'trusted_indexer')), now: () => now }) + await verifyPacket(packet(), d) + const rec = d.checkpoint.getDeferred(KEY) + expect(rec?.action).toBe('manual-review') + expect(rec?.guid).toBe('0xguid') + expect(rec?.message).toBe('0xmsg') + + // Owner approves; the release path must deliver too. + d.checkpoint.addApproval(PAYLOAD) + await processDeferred({ ...d, now: () => now }) + expect(d.execute).toHaveBeenCalledOnce() + }) + + /** + * The LayerZero executor watches the same pathway and sometimes commits first. The ULN reports + * "already committed" with the same revert it uses for "not verified yet", so the destination's + * state is what tells a lost race from a real failure. + */ + describe('racing the executor', () => { + const failCommit = () => vi.fn(async () => { throw new Error('execution reverted') }) + + it('reports a commit the executor already made as done, and still delivers', async () => { + const d = deps({ commit: failCommit(), commitState: vi.fn(async () => 'committed') }) + await verifyPacket(packet(), d) + expect(d.execute).toHaveBeenCalledOnce() // committed by someone — ours to deliver + const text = await d.metrics.registry.metrics() + expect(text).toMatch(/dvn_commits_total\{[^}]*result="raced"[^}]*\} 1/) + expect(text).not.toMatch(/dvn_commits_total\{[^}]*result="failure"/) + }) + + it('skips delivery when the executor already executed the packet', async () => { + const d = deps({ commit: failCommit(), commitState: vi.fn(async () => 'cleared') }) + await verifyPacket(packet(), d) + expect(d.execute).not.toHaveBeenCalled() + expect(d.checkpoint.isProcessed(KEY)).toBe(true) + }) + + it('reports a lost delivery race as delivered, not failed', async () => { + const d = deps({ + execute: vi.fn(async () => { throw new Error('execution reverted') }), + commitState: vi.fn(async () => 'cleared'), + }) + await verifyPacket(packet(), d) + const text = await d.metrics.registry.metrics() + expect(text).toMatch(/dvn_deliveries_total\{[^}]*result="raced"[^}]*\} 1/) + }) + + // The distinction must never soften a real failure: nothing committed means nothing committed. + it('still reports a real commit failure when the destination has nothing', async () => { + const d = deps({ commit: failCommit(), commitState: vi.fn(async () => 'pending') }) + await verifyPacket(packet(), d) + expect(d.execute).not.toHaveBeenCalled() + const text = await d.metrics.registry.metrics() + expect(text).toMatch(/dvn_commits_total\{[^}]*result="failure"[^}]*\} 1/) + }) + + it('treats an unreadable destination as a failure rather than a race', async () => { + const d = deps({ + commit: failCommit(), + commitState: vi.fn(async () => { throw new Error('rpc down') }), + }) + await verifyPacket(packet(), d) + const text = await d.metrics.registry.metrics() + expect(text).toMatch(/dvn_commits_total\{[^}]*result="failure"[^}]*\} 1/) }) - expect(verify).not.toHaveBeenCalled() }) it('still marks processed when commit fails (verification already on-chain)', async () => { - const cp = tmpCheckpoint() - const verify = vi.fn(async () => '0xverifytx') - const commit = vi.fn(async () => { throw new Error('commit not ready') }) - const metrics = createMetrics() - await verifyPacket(packet(), { - assessor: makeAssessor(new Denylist()), - resolveDst, verify, commit, - checkpoint: cp, metrics, logger: silent, srcChainKey: 'baseSepolia', - }) - expect(verify).toHaveBeenCalledOnce() - expect(cp.isProcessed('0x' + 'a'.repeat(64) + ':40232')).toBe(true) - const text = await metrics.registry.metrics() + const d = deps({ commit: vi.fn(async () => { throw new Error('commit not ready') }) }) + await verifyPacket(packet(), d) + expect(d.verify).toHaveBeenCalledOnce() + expect(d.checkpoint.isProcessed(KEY)).toBe(true) + const text = await d.metrics.registry.metrics() expect(text).toMatch(/dvn_commits_total\{[^}]*result="failure"[^}]*\} 1/) }) + + // The scan cursor advances past the packet's block whether or not the send landed, so a + // failed send must leave a deferred record behind — "unprocessed" alone is a packet that is + // never presented again, i.e. lost. + it('defers the packet when submitVerification fails, so the queue retries it', async () => { + const d = deps({ verify: vi.fn(async () => { throw new Error('nonce too low') }) }) + await verifyPacket(packet(), d) + expect(d.checkpoint.isProcessed(KEY)).toBe(false) + expect(d.commit).not.toHaveBeenCalled() + const rec = d.checkpoint.getDeferred(KEY) + expect(rec?.action).toBe('delay') + expect(rec?.attempts).toBe(0) + }) + + it('recovers a send-failed packet end-to-end once verify works again', async () => { + const now = 1_000_000 + const d = deps({ + verify: vi.fn(async () => { throw new Error('ETIMEDOUT') }), + now: () => now, + }) + await verifyPacket(packet(), d) + expect(d.checkpoint.getDeferred(KEY)).toBeDefined() + + // Verify comes back; the delay queue re-screens (still clean) and the send lands. + d.verify.mockImplementation(async () => '0xverifytx') + await processDeferred({ ...d, now: () => now + DELAY_POLICY.retryAfterMs + 1 }) + expect(d.verify).toHaveBeenCalledTimes(2) + expect(d.checkpoint.isProcessed(KEY)).toBe(true) + expect(d.checkpoint.getDeferred(KEY)).toBeUndefined() + }) + + it('escalates a persistently send-failing packet to manual review instead of retrying forever', async () => { + let now = 1_000_000 + const d = deps({ + verify: vi.fn(async () => { throw new Error('ETIMEDOUT') }), + now: () => now, + }) + await verifyPacket(packet(), d) + + for (let i = 0; i < DELAY_POLICY.maxAttempts; i++) { + now += DELAY_POLICY.retryAfterMs + 1 + await processDeferred(d) + } + const rec = d.checkpoint.getDeferred(KEY) + expect(rec?.action).toBe('manual-review') + expect(d.checkpoint.isProcessed(KEY)).toBe(false) + }) +}) + +describe('verdict emission', () => { + it('ALLOW: carries the verdict on submitVerification, with no extra transaction', async () => { + const d = deps() + await verifyPacket(packet(), d) + const verdict = d.verify.mock.calls[0][3] + expect(verdict.action).toBe(ACTION_CODES.allow) + expect(verdict.score).toBe(0) + expect(verdict.evidenceHash).toMatch(/^0x[0-9a-f]{64}$/) + expect(d.recordVerdict).not.toHaveBeenCalled() // no separate tx for an allow + }) + + it('counts screening evidence by type and source, for the dashboard', async () => { + const d = deps({ assessor: makeAssessor(storeWith(['sanctions'])) }) + await verifyPacket(packet(), d) + const text = await d.metrics.registry.metrics() + expect(text).toMatch(/dvn_screening_evidence_total\{(?=[^}]*type="sanctions")(?=[^}]*source="ofac")[^}]*\} 1/) + }) + + it('BLOCK: records the verdict in a separate transaction with the reason mask', async () => { + const d = deps({ assessor: makeAssessor(storeWith(['sanctions'])) }) + await verifyPacket(packet(), d) + expect(d.verify).not.toHaveBeenCalled() + expect(d.recordVerdict).toHaveBeenCalledOnce() + const [, payloadHash, verdict] = d.recordVerdict.mock.calls[0] + expect(payloadHash).toBe(PAYLOAD) + expect(verdict.action).toBe(ACTION_CODES.block) + expect(verdict.score).toBe(100) + expect(decodeReasonMask(verdict.reasonMask)).toEqual(['sanctions']) + const text = await d.metrics.registry.metrics() + expect(text).toMatch(/dvn_verdict_records_total\{[^}]*result="success"[^}]*\} 1/) + }) + + // Withholding the attestation is what stops the packet; the record is only the audit trail. + it('still enforces the veto when recording it fails', async () => { + const d = deps({ + assessor: makeAssessor(storeWith(['sanctions'])), + recordVerdict: vi.fn(async () => { throw new Error('rpc down') }), + }) + await verifyPacket(packet(), d) + expect(d.verify).not.toHaveBeenCalled() // still vetoed + expect(d.checkpoint.isProcessed(KEY)).toBe(true) // still settled + const text = await d.metrics.registry.metrics() + expect(text).toMatch(/dvn_verdict_records_total\{[^}]*result="failure"[^}]*\} 1/) + }) + + it('does not emit for a held packet when the action is not configured', async () => { + const d = deps({ assessor: makeAssessor(storeWith(['sanctions_1hop'], 'trusted_indexer')) }) + await verifyPacket(packet(), d) + expect(d.recordVerdict).not.toHaveBeenCalled() // emitVerdictFor is {block} + }) + + it('emits for a held packet when the action IS configured', async () => { + const d = deps({ + assessor: makeAssessor(storeWith(['sanctions_1hop'], 'trusted_indexer')), + emitVerdictFor: new Set(['block', 'manual-review']), + }) + await verifyPacket(packet(), d) + expect(d.recordVerdict).toHaveBeenCalledOnce() + expect(d.recordVerdict.mock.calls[0][2].action).toBe(ACTION_CODES['manual-review']) + }) + + it('works with no recordVerdict wired at all', async () => { + const d = deps({ assessor: makeAssessor(storeWith(['sanctions'])), recordVerdict: undefined }) + await verifyPacket(packet(), d) + expect(d.checkpoint.isProcessed(KEY)).toBe(true) // enforced regardless + }) + + it('reports an owner-approved release as allow, flagged owner_approved', async () => { + const t0 = 1_000_000 + const d = deps({ assessor: makeAssessor(storeWith(['sanctions_1hop'], 'trusted_indexer')), now: () => t0 }) + await verifyPacket(packet(), d) + d.checkpoint.addApproval(PAYLOAD) + await processDeferred({ ...d, now: () => t0 + 1000 }) + + const verdict = d.verify.mock.calls[0][3] + expect(verdict.action).toBe(ACTION_CODES.allow) // the contract accepts only allow here + expect(decodeReasonMask(verdict.reasonMask).sort()).toEqual(['owner_approved', 'sanctions_1hop']) + expect(verdict.score).toBe(70) // the score is not rewritten + }) + + it('emits once on escalation, not on every delay retry', async () => { + let t = 1_000_000 + const d = deps({ + assessor: makeAssessor(storeWith(['contract_admin_risk'], 'trusted_indexer')), + emitVerdictFor: new Set(['block', 'manual-review']), + now: () => t, + }) + await verifyPacket(packet(), d) + expect(d.recordVerdict).not.toHaveBeenCalled() // delay is not in emitVerdictFor + for (let i = 0; i < DELAY_POLICY.maxAttempts; i++) { + t += DELAY_POLICY.retryAfterMs + await processDeferred({ ...d, now: () => t }) + } + // One emission for the escalation to manual-review, not one per retry. + expect(d.recordVerdict).toHaveBeenCalledOnce() + expect(d.recordVerdict.mock.calls[0][2].action).toBe(ACTION_CODES['manual-review']) + }) +}) + +describe('processDeferred', () => { + /** Hold a packet, then hand back deps for the second pass. */ + async function held(labels: string[], now: number) { + const d = deps({ assessor: makeAssessor(storeWith(labels, 'trusted_indexer')), now: () => now }) + await verifyPacket(packet(), d) + return d + } + + it('leaves a delayed packet alone before retryAfter', async () => { + const t0 = 1_000_000 + const d = await held(['contract_admin_risk'], t0) + await processDeferred({ ...d, now: () => t0 + 1000 }) + expect(d.verify).not.toHaveBeenCalled() + expect(d.checkpoint.getDeferred(KEY)!.attempts).toBe(0) + }) + + it('re-screens a due delay and releases it when it now scores clean', async () => { + const t0 = 1_000_000 + const d = await held(['contract_admin_risk'], t0) + // The risk store no longer knows anything about the sender. + await processDeferred({ ...d, assessor: makeAssessor(new RiskStore()), now: () => t0 + DELAY_POLICY.retryAfterMs }) + expect(d.verify).toHaveBeenCalledOnce() + expect(d.commit).toHaveBeenCalledOnce() + expect(d.checkpoint.isProcessed(KEY)).toBe(true) + expect(d.checkpoint.getDeferred(KEY)).toBeUndefined() + }) + + it('vetoes a due delay that has since become a direct hit', async () => { + const t0 = 1_000_000 + const d = await held(['contract_admin_risk'], t0) + await processDeferred({ + ...d, + assessor: makeAssessor(storeWith(['sanctions'])), + now: () => t0 + DELAY_POLICY.retryAfterMs, + }) + expect(d.verify).not.toHaveBeenCalled() + expect(d.checkpoint.isProcessed(KEY)).toBe(true) + expect(d.checkpoint.getDeferred(KEY)).toBeUndefined() + }) + + it('escalates to manual-review once maxAttempts is exhausted', async () => { + let t = 1_000_000 + const d = await held(['contract_admin_risk'], t) + for (let i = 0; i < DELAY_POLICY.maxAttempts; i++) { + t += DELAY_POLICY.retryAfterMs + await processDeferred({ ...d, now: () => t }) + } + const rec = d.checkpoint.getDeferred(KEY)! + expect(rec.action).toBe('manual-review') + expect(rec.attempts).toBe(DELAY_POLICY.maxAttempts) + expect(d.verify).not.toHaveBeenCalled() + const text = await d.metrics.registry.metrics() + expect(text).toMatch(/dvn_decisions_total\{[^}]*action="manual-review"[^}]*\} 1/) + }) + + it('promotes a delay straight to manual-review when the score rises', async () => { + const t0 = 1_000_000 + const d = await held(['contract_admin_risk'], t0) + await processDeferred({ + ...d, + assessor: makeAssessor(storeWith(['sanctions_1hop'], 'trusted_indexer')), + now: () => t0 + DELAY_POLICY.retryAfterMs, + }) + expect(d.checkpoint.getDeferred(KEY)!.action).toBe('manual-review') + }) + + it('never releases a manual-review hold on the clock alone', async () => { + const t0 = 1_000_000 + const d = await held(['sanctions_1hop'], t0) + await processDeferred({ ...d, now: () => t0 + 365 * 24 * 3600_000 }) + expect(d.verify).not.toHaveBeenCalled() + expect(d.checkpoint.getDeferred(KEY)!.action).toBe('manual-review') + }) + + it('releases a manual-review hold on an owner approval', async () => { + const t0 = 1_000_000 + const d = await held(['sanctions_1hop'], t0) + d.checkpoint.addApproval(PAYLOAD) + await processDeferred({ ...d, now: () => t0 + 1000 }) + expect(d.verify).toHaveBeenCalledOnce() + expect(d.commit).toHaveBeenCalledOnce() + expect(d.checkpoint.isProcessed(KEY)).toBe(true) + const text = await d.metrics.registry.metrics() + expect(text).toMatch(/dvn_pending_packets\{[^}]*action="manual-review"[^}]*\} 0/) + }) + + it('REFUSES an approval when the packet has since become a direct sanctions hit', async () => { + const t0 = 1_000_000 + const d = await held(['sanctions_1hop'], t0) + d.checkpoint.addApproval(PAYLOAD) + await processDeferred({ + ...d, + assessor: makeAssessor(storeWith(['sanctions'])), // OFAC direct hit landed after approval + now: () => t0 + 1000, + }) + expect(d.verify).not.toHaveBeenCalled() + expect(d.checkpoint.isProcessed(KEY)).toBe(true) // settled as a veto, not released + }) + + it('KEEPS the hold when releasing it fails to send — the packet must not be lost', async () => { + const t0 = 1_000_000 + const d = await held(['sanctions_1hop'], t0) + d.checkpoint.addApproval(PAYLOAD) + const failing = { ...d, verify: vi.fn(async () => { throw new Error('rpc down') }), now: () => t0 + 1000 } + await processDeferred(failing) + expect(d.checkpoint.isProcessed(KEY)).toBe(false) + expect(d.checkpoint.getDeferred(KEY)).toBeDefined() // still held, retried next tick + + // ...and the next pass, with a working RPC, still releases it. + await processDeferred({ ...d, now: () => t0 + 2000 }) + expect(d.verify).toHaveBeenCalledOnce() + expect(d.checkpoint.isProcessed(KEY)).toBe(true) + }) + + it('drops a deferred record for a packet that was settled elsewhere', async () => { + const t0 = 1_000_000 + const d = await held(['sanctions_1hop'], t0) + d.checkpoint.markProcessed(KEY) + await processDeferred({ ...d, now: () => t0 + 1000 }) + expect(d.checkpoint.getDeferred(KEY)).toBeUndefined() + expect(d.verify).not.toHaveBeenCalled() + }) + + it('survives a restart — holds and approvals are persisted', async () => { + const dir = mkdtempSync(join(tmpdir(), 'dvn-cp-')) + const path = join(dir, 'cp.json') + const t0 = 1_000_000 + + const first = deps({ + assessor: makeAssessor(storeWith(['sanctions_1hop'], 'trusted_indexer')), + checkpoint: new Checkpoint(path), + now: () => t0, + }) + await verifyPacket(packet(), first) + first.checkpoint.addApproval(PAYLOAD) + first.checkpoint.save() + + const reloaded = new Checkpoint(path) + expect(reloaded.getDeferred(KEY)!.action).toBe('manual-review') + expect(reloaded.isApproved(PAYLOAD)).toBe(true) + + const second = deps({ checkpoint: reloaded, now: () => t0 + 1000 }) + await processDeferred(second) + expect(second.verify).toHaveBeenCalledOnce() + }) +}) + +/** + * Owner rejection. Refusal is not a DVN call: the owner skips the nonce on the endpoint, which + * makes the packet permanently unexecutable, and the worker's only job is to stop carrying it. + */ +describe('processDeferred: owner rejection by skipped nonce', () => { + /** A held manual-review packet, ready to be reconsidered. */ + async function heldPacket(overrides: Record = {}) { + const d = deps({ assessor: makeAssessor(storeWith(['sanctions_1hop'], 'trusted_indexer')), ...overrides }) + await verifyPacket(packet(), d) + expect(d.checkpoint.getDeferred(KEY)?.action).toBe('manual-review') + return d + } + + it('drops a held packet whose nonce the owner skipped', async () => { + const abandoned = vi.fn(async () => true) + const d = await heldPacket({ abandoned }) + await processDeferred(d) + + expect(abandoned).toHaveBeenCalledWith(optChain, '0xheader') + expect(d.checkpoint.getDeferred(KEY)).toBeUndefined() + expect(d.checkpoint.isProcessed(KEY)).toBe(true) + expect(d.verify).not.toHaveBeenCalled() + const text = await d.metrics.registry.metrics() + expect(text).toMatch(/dvn_decisions_total\{[^}]*action="rejected"[^}]*\} 1/) + }) + + // The check has to beat the manual-review early-continue, or the one kind of hold a human + // actually rejects would never be looked at. + it('checks manual-review holds, which are otherwise skipped without re-screening', async () => { + const abandoned = vi.fn(async () => false) + const d = await heldPacket({ abandoned }) + await processDeferred(d) + expect(abandoned).toHaveBeenCalledOnce() + expect(d.checkpoint.getDeferred(KEY)?.action).toBe('manual-review') // still held + }) + + it('keeps the hold when the check cannot be made', async () => { + const d = await heldPacket({ abandoned: vi.fn(async () => { throw new Error('rpc down') }) }) + await processDeferred(d) + expect(d.checkpoint.getDeferred(KEY)?.action).toBe('manual-review') + expect(d.checkpoint.isProcessed(KEY)).toBe(false) + }) + + // The endpoint has already made the packet undeliverable, so releasing it would only spend gas + // verifying something that can never execute. + it('rejection wins over an approval, since the chain will not carry the packet either way', async () => { + const d = await heldPacket({ abandoned: vi.fn(async () => true) }) + d.checkpoint.addApproval(PAYLOAD) + await processDeferred(d) + expect(d.verify).not.toHaveBeenCalled() + expect(d.checkpoint.getDeferred(KEY)).toBeUndefined() + }) + + it('leaves the queue alone when no rejection check is wired', async () => { + const d = await heldPacket() + await processDeferred(d) + expect(d.checkpoint.getDeferred(KEY)?.action).toBe('manual-review') + }) }) diff --git a/worker/test/store.spec.ts b/worker/test/store.spec.ts index 9adcf0f..f5fe6a7 100644 --- a/worker/test/store.spec.ts +++ b/worker/test/store.spec.ts @@ -1,21 +1,74 @@ import { describe, it, expect } from 'vitest' -import { Denylist } from '../assess/store' +import { RiskStore } from '../assess/store' -describe('Denylist', () => { +describe('RiskStore', () => { it('normalizes case and detects membership', () => { - const dl = new Denylist() - dl.add('0xAAbbCC', 'ofac', 'SDN match') - expect(dl.has('0xaabbcc')).toBe(true) - expect(dl.lookup('0xAABBCC')?.tags).toContain('ofac') - expect(dl.has('0x000001')).toBe(false) + const store = new RiskStore() + store.upsert({ subject: '0xAAbbCC', labels: ['sanctions'], source: 'ofac' }) + expect(store.has('0xaabbcc')).toBe(true) + expect(store.lookup('0xAABBCC')[0].labels).toContain('sanctions') + expect(store.has('0x000001')).toBe(false) }) - it('merges multiple sources for the same address', () => { - const dl = new Denylist() - dl.add('0x01', 'ofac', 'r1') - dl.add('0x01', 'mixer', 'r2') - const e = dl.lookup('0x01')! - expect(e.tags.sort()).toEqual(['mixer', 'ofac']) - expect(e.reasons.length).toBe(2) + it('keeps one entry per source so each keeps its own TTL and confidence', () => { + const store = new RiskStore() + store.upsert({ subject: '0x01', labels: ['sanctions'], source: 'ofac' }) + store.upsert({ subject: '0x01', labels: ['mixer_exposure'], source: 'trusted_indexer' }) + const entries = store.lookup('0x01') + expect(entries.length).toBe(2) + expect(entries.map((e) => e.source).sort()).toEqual(['ofac', 'trusted_indexer']) + expect(store.size).toBe(1) // one subject, two assertions + }) + + it('merges labels and refreshes lastSeen when the same source re-asserts', () => { + let t = 1000 + const store = new RiskStore({ now: () => t }) + store.upsert({ subject: '0x01', labels: ['sanctions'], source: 'ofac' }) + t = 5000 + store.upsert({ subject: '0x01', labels: ['sanctioned_mixer'], source: 'ofac' }) + const entries = store.lookup('0x01') + expect(entries.length).toBe(1) + expect(entries[0].labels.sort()).toEqual(['sanctioned_mixer', 'sanctions']) + expect(entries[0].firstSeen).toBe(1000) + expect(entries[0].lastSeen).toBe(5000) + }) + + it('defaults confidence from the source trust table', () => { + const store = new RiskStore() + store.upsert({ subject: '0x01', labels: ['sanctions'], source: 'ofac' }) + store.upsert({ subject: '0x02', labels: ['mixer_exposure'], source: 'public_event' }) + expect(store.lookup('0x01')[0].confidence).toBe(1) + expect(store.lookup('0x02')[0].confidence).toBe(0.3) + }) + + it('hides expired entries without hiding live ones for the same subject', () => { + let t = 1000 + const store = new RiskStore({ now: () => t }) + store.upsert({ subject: '0x01', labels: ['sanctions'], source: 'ofac' }) // no expiry + store.upsert({ subject: '0x01', labels: ['mixer_exposure'], source: 'trusted_indexer', expiresAt: 2000 }) + expect(store.lookup('0x01').length).toBe(2) + t = 2001 + const live = store.lookup('0x01') + expect(live.length).toBe(1) + expect(live[0].source).toBe('ofac') // the permanent sanctions label survives the feed's TTL + }) + + it('prunes expired entries and drops subjects left empty', () => { + let t = 1000 + const store = new RiskStore({ now: () => t }) + store.upsert({ subject: '0x01', labels: ['mixer_exposure'], source: 'trusted_indexer', expiresAt: 2000 }) + store.upsert({ subject: '0x02', labels: ['sanctions'], source: 'ofac' }) + t = 2001 + expect(store.prune()).toBe(1) + expect(store.has('0x01')).toBe(false) + expect(store.has('0x02')).toBe(true) + }) + + it('counts live entries per source', () => { + const store = new RiskStore() + store.upsert({ subject: '0x01', labels: ['sanctions'], source: 'ofac' }) + store.upsert({ subject: '0x02', labels: ['sanctions'], source: 'ofac' }) + store.upsert({ subject: '0x02', labels: ['mixer_exposure'], source: 'trusted_indexer' }) + expect(store.countsBySource()).toEqual({ ofac: 2, trusted_indexer: 1 }) }) }) diff --git a/worker/test/token-provider.spec.ts b/worker/test/token-provider.spec.ts new file mode 100644 index 0000000..8388bc1 --- /dev/null +++ b/worker/test/token-provider.spec.ts @@ -0,0 +1,233 @@ +import { describe, it, expect, vi } from 'vitest' +import { + RpcTokenInspector, + decodeStringReturn, + isRevert, + isFakeStablecoin, + CANONICAL_STABLECOINS, +} from '../assess/providers/token' +import { loadScamTokens } from '../assess/ingest/tokens' +import { RiskStore } from '../assess/store' +import type { ChainReader } from '../assess/providers/contract' + +const OAPP = '0x' + 'a'.repeat(40) +const TOKEN = '0x' + 'b'.repeat(40) +const EMPTY_WORD = '0x' + '0'.repeat(64) + +const SELECTOR_TOKEN = '0xfc0c546a' +const SELECTOR_SYMBOL = '0x95d89b41' +const SELECTOR_DECIMALS = '0x313ce567' + +const word = (addr: string) => '0x' + addr.replace(/^0x/, '').padStart(64, '0') +const uint = (n: number) => '0x' + n.toString(16).padStart(64, '0') + +/** ABI-encode a dynamic string the way `symbol()` returns one. */ +function encodeString(s: string): string { + const bytes = Buffer.from(s, 'utf8').toString('hex') + const padded = bytes.padEnd(Math.ceil(bytes.length / 64) * 64 || 64, '0') + return '0x' + uint(32).slice(2) + uint(s.length).slice(2) + padded +} + +/** ethers surfaces a revert as CALL_EXCEPTION; mimic that so classification is exercised. */ +const revert = () => Object.assign(new Error('call revert exception'), { code: 'CALL_EXCEPTION' }) + +function reader(handlers: Record Promise> = {}): ChainReader { + return { + getCode: async () => '0x60006000', + getStorageAt: async () => EMPTY_WORD, + call: async ({ data }) => { + const h = handlers[data] + if (h) return h() + return EMPTY_WORD + }, + } +} + +describe('decodeStringReturn', () => { + it('decodes a dynamic string', () => { + expect(decodeStringReturn(encodeString('USDC'))).toBe('USDC') + expect(decodeStringReturn(encodeString('Wrapped Ether'))).toBe('Wrapped Ether') + }) + + it('decodes a legacy bytes32 symbol', () => { + const b32 = '0x' + Buffer.from('DAI', 'utf8').toString('hex').padEnd(64, '0') + expect(decodeStringReturn(b32)).toBe('DAI') + }) + + it('returns undefined for empty or nonsense returndata', () => { + expect(decodeStringReturn('0x')).toBeUndefined() + expect(decodeStringReturn('')).toBeUndefined() + expect(decodeStringReturn(EMPTY_WORD)).toBeUndefined() + }) + + it('rejects an absurd declared length instead of allocating on it', () => { + const bogus = '0x' + uint(32).slice(2) + uint(1_000_000).slice(2) + '00'.repeat(32) + expect(decodeStringReturn(bogus)).toBeUndefined() + }) +}) + +describe('isRevert', () => { + it('recognises an ethers CALL_EXCEPTION', () => { + expect(isRevert(revert())).toBe(true) + }) + + it('recognises a revert reported only as a message', () => { + expect(isRevert(new Error('execution reverted: no such function'))).toBe(true) + }) + + it('does NOT treat a transport failure as a revert', () => { + expect(isRevert(new Error('connect ETIMEDOUT'))).toBe(false) + expect(isRevert(new Error('token() timed out after 3000ms'))).toBe(false) + expect(isRevert(undefined)).toBe(false) + }) +}) + +describe('RpcTokenInspector.resolveToken', () => { + it('resolves the underlying token of an OFT', async () => { + const inspector = new RpcTokenInspector({ + readers: { base: reader({ [SELECTOR_TOKEN]: async () => word(TOKEN) }) }, + }) + expect(await inspector.resolveToken(OAPP, 'base')).toEqual({ kind: 'token', address: TOKEN }) + }) + + it('reports not-a-token when token() reverts', async () => { + const inspector = new RpcTokenInspector({ + readers: { base: reader({ [SELECTOR_TOKEN]: async () => { throw revert() } }) }, + }) + expect(await inspector.resolveToken(OAPP, 'base')).toEqual({ kind: 'not-a-token' }) + }) + + it('reports not-a-token when token() returns the zero address', async () => { + const inspector = new RpcTokenInspector({ readers: { base: reader() } }) + expect(await inspector.resolveToken(OAPP, 'base')).toEqual({ kind: 'not-a-token' }) + }) + + // The distinction that keeps an RPC outage from silently skipping token screening. + it('reports unknown — NOT not-a-token — on a transport failure', async () => { + const inspector = new RpcTokenInspector({ + readers: { base: reader({ [SELECTOR_TOKEN]: async () => { throw new Error('connect ECONNREFUSED') } }) }, + }) + const r = await inspector.resolveToken(OAPP, 'base') + expect(r.kind).toBe('unknown') + }) + + it('reports unknown on a timeout rather than hanging', async () => { + const inspector = new RpcTokenInspector({ + readers: { base: reader({ [SELECTOR_TOKEN]: () => new Promise(() => {}) }) }, + timeoutMs: 10, + }) + const r = await inspector.resolveToken(OAPP, 'base') + expect(r).toMatchObject({ kind: 'unknown' }) + expect((r as { reason: string }).reason).toMatch(/timed out/) + }) + + it('reports unknown for an unconfigured chain', async () => { + const inspector = new RpcTokenInspector({ readers: { base: reader() } }) + expect((await inspector.resolveToken(OAPP, 'nope')).kind).toBe('unknown') + }) + + it('caches a definite resolution but never an unknown one', async () => { + let fail = true + const call = vi.fn(async () => { + if (fail) throw new Error('connect ECONNREFUSED') + return word(TOKEN) + }) + const inspector = new RpcTokenInspector({ readers: { base: { ...reader(), call } } }) + + expect((await inspector.resolveToken(OAPP, 'base')).kind).toBe('unknown') + fail = false + expect(await inspector.resolveToken(OAPP, 'base')).toEqual({ kind: 'token', address: TOKEN }) + expect(call).toHaveBeenCalledTimes(2) + await inspector.resolveToken(OAPP, 'base') // now served from cache + expect(call).toHaveBeenCalledTimes(2) + }) +}) + +describe('RpcTokenInspector.inspect', () => { + it('reads symbol and decimals', async () => { + const inspector = new RpcTokenInspector({ + readers: { + base: reader({ + [SELECTOR_SYMBOL]: async () => encodeString('USDC'), + [SELECTOR_DECIMALS]: async () => uint(6), + }), + }, + }) + expect(await inspector.inspect(TOKEN, 'base')).toEqual({ address: TOKEN, symbol: 'USDC', decimals: 6 }) + }) + + it('tolerates missing metadata — both fields are optional per ERC-20', async () => { + const inspector = new RpcTokenInspector({ + readers: { + base: reader({ + [SELECTOR_SYMBOL]: async () => { throw revert() }, + [SELECTOR_DECIMALS]: async () => { throw revert() }, + }), + }, + }) + expect(await inspector.inspect(TOKEN, 'base')).toEqual({ + address: TOKEN, + symbol: undefined, + decimals: undefined, + }) + }) + + it('rejects on timeout so the caller can hold the packet', async () => { + const inspector = new RpcTokenInspector({ + readers: { base: reader({ [SELECTOR_SYMBOL]: () => new Promise(() => {}) }) }, + timeoutMs: 10, + }) + await expect(inspector.inspect(TOKEN, 'base')).rejects.toThrow(/timed out/) + }) + + it('rejects for an unconfigured chain rather than reporting empty metadata', async () => { + const inspector = new RpcTokenInspector({ readers: { base: reader() } }) + await expect(inspector.inspect(TOKEN, 'nope')).rejects.toThrow(/no chain reader configured/) + }) +}) + +describe('isFakeStablecoin', () => { + const chain = 'baseSepolia' + const canonical = CANONICAL_STABLECOINS[chain].USDC + + it('flags a watched symbol at a non-canonical address', () => { + expect(isFakeStablecoin({ address: TOKEN, symbol: 'USDC' }, chain)).toBe(true) + }) + + it('accepts the canonical address, case-insensitively', () => { + expect(isFakeStablecoin({ address: canonical, symbol: 'USDC' }, chain)).toBe(false) + expect(isFakeStablecoin({ address: canonical.toUpperCase(), symbol: 'usdc' }, chain)).toBe(false) + }) + + it('ignores unwatched symbols', () => { + expect(isFakeStablecoin({ address: TOKEN, symbol: 'WETH' }, chain)).toBe(false) + }) + + it('ignores a token with no symbol', () => { + expect(isFakeStablecoin({ address: TOKEN }, chain)).toBe(false) + }) + + // Guessing would flag the genuine token, so an unknown pairing yields no judgement. + it('does not judge when the chain has no canonical entry for the symbol', () => { + expect(isFakeStablecoin({ address: TOKEN, symbol: 'USDT' }, chain)).toBe(false) + expect(isFakeStablecoin({ address: TOKEN, symbol: 'USDC' }, 'someUnlistedChain')).toBe(false) + }) +}) + +describe('loadScamTokens', () => { + it('ships empty when no env value is set', () => { + const store = new RiskStore() + expect(loadScamTokens(store, '')).toBe(0) + expect(store.size).toBe(0) + }) + + it('loads addresses as operator-sourced scam tokens', () => { + const store = new RiskStore() + expect(loadScamTokens(store, `${TOKEN},0xNOTANADDRESS,${OAPP.toUpperCase()}`)).toBe(2) + const entry = store.lookup(TOKEN)[0] + expect(entry.labels).toEqual(['scam_token']) + expect(entry.subjectType).toBe('token') + expect(entry.source).toBe('operator') + expect(store.has(OAPP)).toBe(true) // normalized to lowercase + }) +}) diff --git a/worker/test/trace.spec.ts b/worker/test/trace.spec.ts deleted file mode 100644 index 447afd7..0000000 --- a/worker/test/trace.spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { describe, it, expect } from 'vitest' -import { buildTrace } from '../tracker/trace' -import { Denylist } from '../assess/store' -import { makeAssessor } from '../assess/assess' - -describe('buildTrace', () => { - it('colors endpoints with assess() and reports status', () => { - const dl = new Denylist() - dl.add('0x00000000000000000000000000000000000000aa', 'ofac', 'sdn') - const assess = makeAssessor(dl) - const apiResponse = { - data: [{ - pathway: { - srcEid: 40232, dstEid: 40245, - sender: { address: '0x00000000000000000000000000000000000000AA' }, - receiver: { address: '0x00000000000000000000000000000000000000bb' }, - }, - status: { name: 'INFLIGHT' }, guid: '0xguid', - }], - } - const t = buildTrace(apiResponse, assess) - expect(t.srcEid).toBe(40232) - expect(t.dstEid).toBe(40245) - expect(t.status).toBe('INFLIGHT') - expect(t.sender.blocked).toBe(true) - expect(t.receiver.blocked).toBe(false) - }) -}) diff --git a/worker/test/tx-sender.spec.ts b/worker/test/tx-sender.spec.ts index aa3096b..66308fe 100644 --- a/worker/test/tx-sender.spec.ts +++ b/worker/test/tx-sender.spec.ts @@ -171,4 +171,19 @@ describe('isRetriableTxError', () => { expect(isRetriableTxError({ code: 'CALL_EXCEPTION' } as Error & { code: string })).toBe(false) expect(isRetriableTxError(new Error('boom'))).toBe(false) }) + + // A failed gas estimate arrives under one code whether the node hiccuped or the call reverts. + // Retrying a revert only spends the backoff and reports a settled outcome as a transient one. + it('separates a reverting gas estimate from a flaky one', () => { + const reverting = Object.assign( + new Error('cannot estimate gas ... (error={"reason":"execution reverted","data":"0x4c3118d4"})'), + { code: 'UNPREDICTABLE_GAS_LIMIT' }, + ) + expect(isRetriableTxError(reverting)).toBe(false) + + const flaky = Object.assign(new Error('cannot estimate gas; upstream timeout'), { + code: 'UNPREDICTABLE_GAS_LIMIT', + }) + expect(isRetriableTxError(flaky)).toBe(true) + }) }) diff --git a/worker/test/verdict.spec.ts b/worker/test/verdict.spec.ts new file mode 100644 index 0000000..85e2a5a --- /dev/null +++ b/worker/test/verdict.spec.ts @@ -0,0 +1,214 @@ +import { describe, it, expect } from 'vitest' +import { + ACTION_CODES, + REASON_BITS, + UNMAPPED_REASON_BIT, + reasonMask, + decodeReasonMask, + buildEvidenceDocument, + evidenceHash, + encodeVerdict, +} from '../assess/verdict' +import { POLICY_VERSION } from '../assess/policy' +import type { Assessment } from '../assess/assess' + +const PAYLOAD = '0x' + 'a'.repeat(64) +const A = '0x' + '1'.repeat(40) +const PARTIES = [ + { subject: A, chainKey: 'baseSepolia' }, + { subject: '0x' + '2'.repeat(40), chainKey: 'optimismSepolia' }, +] + +function assessment(overrides: Partial = {}): Assessment { + return { + subject: A, + score: 70, + action: 'manual-review', + reasonCodes: ['sanctions_1hop'], + evidence: [ + { type: 'sanctions_1hop', weight: 70, confidence: 0.8, source: 'trusted_indexer', subject: A }, + ], + ...overrides, + } +} + +describe('ACTION_CODES', () => { + // These are part of the event ABI. Changing one silently rewrites the meaning of every log + // already emitted, so the numbering is pinned here and in ComplianceDVN.sol. + it('matches the contract constants', () => { + expect(ACTION_CODES).toEqual({ allow: 0, delay: 1, 'manual-review': 2, block: 3 }) + }) +}) + +describe('REASON_BITS', () => { + it('assigns every bit exactly once', () => { + const bits = Object.values(REASON_BITS) + expect(new Set(bits).size).toBe(bits.length) + }) + + it('leaves the unmapped bit free', () => { + expect(Object.values(REASON_BITS)).not.toContain(UNMAPPED_REASON_BIT) + }) + + it('keeps every bit inside a uint256', () => { + for (const bit of Object.values(REASON_BITS)) { + expect(bit).toBeGreaterThanOrEqual(0) + expect(bit).toBeLessThan(256) + } + }) + + // Pinning the direct-hit assignments guards against an accidental renumbering, which would + // misread historical events rather than fail loudly. + it('pins the established assignments', () => { + expect(REASON_BITS.sanctions).toBe(0) + expect(REASON_BITS.sanctioned_mixer).toBe(1) + expect(REASON_BITS.scam_token).toBe(2) + expect(REASON_BITS.operator_deny).toBe(3) + }) + + it('carries the depth-2/3 proximity codes added in policy v2', () => { + const codes = [ + 'sanctions_2hop', + 'sanctions_3hop', + 'sanctions_2hop_inbound', + 'sanctions_3hop_inbound', + 'mixer_exposure_2hop', + 'mixer_exposure_3hop', + ] + const { mask, unmapped } = reasonMask(codes) + expect(unmapped).toEqual([]) + expect(decodeReasonMask(mask).sort()).toEqual([...codes].sort()) + }) +}) + +describe('reasonMask', () => { + it('packs and round-trips codes', () => { + const { mask, unmapped } = reasonMask(['sanctions', 'mixer_exposure']) + expect(unmapped).toEqual([]) + expect(mask).toBe((1n << 0n) | (1n << 6n)) + expect(decodeReasonMask(mask).sort()).toEqual(['mixer_exposure', 'sanctions']) + }) + + it('is empty for no codes', () => { + expect(reasonMask([]).mask).toBe(0n) + expect(decodeReasonMask(0n)).toEqual([]) + }) + + it('is idempotent for a repeated code', () => { + expect(reasonMask(['sanctions', 'sanctions']).mask).toBe(reasonMask(['sanctions']).mask) + }) + + // Dropping an unknown reason would produce an audit record that quietly omits it. + it('flags an unknown code on the reserved bit instead of dropping it', () => { + const { mask, unmapped } = reasonMask(['sanctions', 'something_new']) + expect(unmapped).toEqual(['something_new']) + expect((mask >> BigInt(UNMAPPED_REASON_BIT)) & 1n).toBe(1n) + expect(decodeReasonMask(mask)).toContain(`unmapped:${UNMAPPED_REASON_BIT}`) + }) +}) + +describe('evidence document', () => { + it('converts confidence to an integer percent so the document can be canonicalized', () => { + const doc = buildEvidenceDocument(PAYLOAD, assessment(), PARTIES) + expect(doc.evidence[0].confidencePct).toBe(80) + expect(() => evidenceHash(doc)).not.toThrow() + }) + + it('commits to the policy version', () => { + expect(buildEvidenceDocument(PAYLOAD, assessment(), PARTIES).policyVersion).toBe(POLICY_VERSION) + }) + + it('lowercases the payload hash and party subjects', () => { + const doc = buildEvidenceDocument(PAYLOAD.toUpperCase().replace('0X', '0x'), assessment(), [ + { subject: A.toUpperCase().replace('0X', '0x'), chainKey: 'baseSepolia' }, + ]) + expect(doc.payloadHash).toBe(PAYLOAD) + expect(doc.parties[0].subject).toBe(A) + }) + + it('hashes deterministically regardless of key insertion order', () => { + const a = buildEvidenceDocument(PAYLOAD, assessment(), PARTIES) + const b = buildEvidenceDocument(PAYLOAD, assessment(), PARTIES) + // Rebuild with the object literal written in a different order. + const reordered = { ...b, score: b.score, policyVersion: b.policyVersion, action: b.action } + expect(evidenceHash(a)).toBe(evidenceHash(b)) + expect(evidenceHash(a)).toBe(evidenceHash(reordered)) + expect(evidenceHash(a)).toMatch(/^0x[0-9a-f]{64}$/) + }) + + it('changes the hash when any committed field changes', () => { + const base = evidenceHash(buildEvidenceDocument(PAYLOAD, assessment(), PARTIES)) + expect(evidenceHash(buildEvidenceDocument(PAYLOAD, assessment({ score: 71 }), PARTIES))).not.toBe(base) + expect(evidenceHash(buildEvidenceDocument(PAYLOAD, assessment({ action: 'block' }), PARTIES))).not.toBe(base) + expect(evidenceHash(buildEvidenceDocument('0x' + 'b'.repeat(64), assessment(), PARTIES))).not.toBe(base) + expect(evidenceHash(buildEvidenceDocument(PAYLOAD, assessment(), [PARTIES[0]]))).not.toBe(base) + }) + + // details hold arbitrary values, including floats a future provider might add, which would + // break hashing at runtime. They are logged, not committed. + it('ignores free-form evidence details', () => { + const withDetails = assessment({ + evidence: [ + { + type: 'sanctions_1hop', + weight: 70, + confidence: 0.8, + source: 'trusted_indexer', + subject: A, + details: { anything: 1.2345 }, + }, + ], + }) + expect(evidenceHash(buildEvidenceDocument(PAYLOAD, withDetails, PARTIES))).toBe( + evidenceHash(buildEvidenceDocument(PAYLOAD, assessment(), PARTIES)), + ) + }) +}) + +describe('encodeVerdict', () => { + it('encodes the action, score, mask, and hash', () => { + const { encoded } = encodeVerdict(PAYLOAD, assessment(), PARTIES) + expect(encoded.action).toBe(ACTION_CODES['manual-review']) + expect(encoded.score).toBe(70) + expect(decodeReasonMask(encoded.reasonMask)).toEqual(['sanctions_1hop']) + expect(encoded.evidenceHash).toMatch(/^0x[0-9a-f]{64}$/) + }) + + // The contract rejects a verification claiming anything but allow, so a release must report + // the action actually taken while keeping the reasons it was held for. + it('overrides the action for an owner-approved release, keeping the original reasons', () => { + const { encoded } = encodeVerdict(PAYLOAD, assessment(), PARTIES, { + overrideAction: 'allow', + extraReasons: ['owner_approved'], + }) + expect(encoded.action).toBe(ACTION_CODES.allow) + expect(decodeReasonMask(encoded.reasonMask).sort()).toEqual(['owner_approved', 'sanctions_1hop']) + expect(encoded.score).toBe(70) // the score is not rewritten, only the action + }) + + it('commits the overridden action to the hash, not the original', () => { + const overridden = encodeVerdict(PAYLOAD, assessment(), PARTIES, { overrideAction: 'allow' }) + const plain = encodeVerdict(PAYLOAD, assessment(), PARTIES) + expect(overridden.encoded.evidenceHash).not.toBe(plain.encoded.evidenceHash) + }) + + it('reports unmapped reason codes to the caller', () => { + const { unmapped } = encodeVerdict(PAYLOAD, assessment({ reasonCodes: ['brand_new'] }), PARTIES) + expect(unmapped).toEqual(['brand_new']) + }) + + it('clamps the score into uint16 rather than overflowing', () => { + expect(encodeVerdict(PAYLOAD, assessment({ score: 999_999 }), PARTIES).encoded.score).toBe(65535) + expect(encodeVerdict(PAYLOAD, assessment({ score: -5 }), PARTIES).encoded.score).toBe(0) + }) + + it('encodes an empty verdict', () => { + const { encoded } = encodeVerdict( + PAYLOAD, + assessment({ score: 0, action: 'allow', reasonCodes: [], evidence: [] }), + PARTIES, + ) + expect(encoded.action).toBe(0) + expect(encoded.reasonMask).toBe(0n) + }) +}) diff --git a/worker/tracker/trace.ts b/worker/tracker/trace.ts deleted file mode 100644 index 03c099c..0000000 --- a/worker/tracker/trace.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { buildDenylist, makeAssessor, Assessor, Assessment } from '../assess/assess' - -const SCAN_TESTNET = 'https://scan-testnet.layerzero-api.com/v1/messages/tx/' - -export interface TraceResult { - guid: string - srcEid: number - dstEid: number - status: string - sender: Assessment - receiver: Assessment -} - -/** Pure: turn a Scan API response into a risk-colored trace. */ -export function buildTrace(api: any, assess: Assessor): TraceResult { - const m = api?.data?.[0] - if (!m) throw new Error('no message found for tx') - return { - guid: m.guid, - srcEid: m.pathway.srcEid, - dstEid: m.pathway.dstEid, - status: m.status?.name ?? 'UNKNOWN', - sender: assess(m.pathway.sender.address), - receiver: assess(m.pathway.receiver.address), - } -} - -/** Fetch from LayerZero Scan + color with a freshly built denylist. */ -export async function trace(txHash: string): Promise { - const fetch = (await import('node-fetch')).default - const res = await fetch(SCAN_TESTNET + txHash) - if (!res.ok) throw new Error(`Scan API ${res.status}`) - const api = await res.json() - return buildTrace(api, makeAssessor(await buildDenylist())) -}