From 9f3880ee168074aef48cd4a2d9d37946692c1178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Berk=20Tav=C5=9Fan?= Date: Sun, 15 Mar 2026 13:57:58 +0300 Subject: [PATCH] Add xlayer-expert AI coding skill to toolkit AI coding assistant skill providing X Layer expertise for smart contract security, gas optimization, and deployment patterns. Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 25 + tools/xlayer-expert/LICENSE | 21 + tools/xlayer-expert/README.md | 134 ++++ tools/xlayer-expert/SKILL.md | 171 +++++ .../references/contract-patterns.md | 313 ++++++++ tools/xlayer-expert/references/flashblocks.md | 100 +++ .../references/gas-optimization.md | 160 +++++ .../references/infrastructure.md | 165 +++++ .../xlayer-expert/references/l2-predeploys.md | 113 +++ .../references/network-config.md | 51 ++ .../references/onchain-data-api.md | 266 +++++++ tools/xlayer-expert/references/security.md | 678 ++++++++++++++++++ .../references/testing-patterns.md | 322 +++++++++ .../references/token-addresses.md | 41 ++ .../references/zkevm-differences.md | 165 +++++ 15 files changed, 2725 insertions(+) create mode 100644 tools/xlayer-expert/LICENSE create mode 100644 tools/xlayer-expert/README.md create mode 100644 tools/xlayer-expert/SKILL.md create mode 100644 tools/xlayer-expert/references/contract-patterns.md create mode 100644 tools/xlayer-expert/references/flashblocks.md create mode 100644 tools/xlayer-expert/references/gas-optimization.md create mode 100644 tools/xlayer-expert/references/infrastructure.md create mode 100644 tools/xlayer-expert/references/l2-predeploys.md create mode 100644 tools/xlayer-expert/references/network-config.md create mode 100644 tools/xlayer-expert/references/onchain-data-api.md create mode 100644 tools/xlayer-expert/references/security.md create mode 100644 tools/xlayer-expert/references/testing-patterns.md create mode 100644 tools/xlayer-expert/references/token-addresses.md create mode 100644 tools/xlayer-expert/references/zkevm-differences.md diff --git a/README.md b/README.md index e129478f..d4e70af5 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,31 @@ Complete local Optimism test environment for development and testing, supporting **Detailed Documentation:** [devnet/README.md](devnet/README.md) +### X Layer Expert (AI Coding Skill) + +AI coding assistant skill that provides deep expertise for building on X Layer — smart contract security, gas optimization, deployment patterns, and more. + +**Quick Start:** +```bash +cd tools/xlayer-expert +# Claude Code +cp -r . ~/.claude/skills/xlayer-expert + +# Or install via skills.sh +npx skills add berktavsan/xlayer-expert +``` + +**Key Features:** +- ✅ 22 Security Golden Rules for Solidity +- ✅ Network config, RPC endpoints, chain IDs +- ✅ Hardhat & Foundry deployment patterns +- ✅ Gas optimization (OKB economics, L1 data fees) +- ✅ Bridge & cross-chain patterns +- ✅ OKLink OnChain Data API integration +- ✅ Works with Claude Code, Cursor, Windsurf, Codex CLI, Gemini CLI + +[📖 Full Documentation →](tools/xlayer-expert/README.md) + ## 🤝 Contributing Issues and Pull Requests are welcome! diff --git a/tools/xlayer-expert/LICENSE b/tools/xlayer-expert/LICENSE new file mode 100644 index 00000000..5bd81d80 --- /dev/null +++ b/tools/xlayer-expert/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Berk Tavsan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/tools/xlayer-expert/README.md b/tools/xlayer-expert/README.md new file mode 100644 index 00000000..43d8d065 --- /dev/null +++ b/tools/xlayer-expert/README.md @@ -0,0 +1,134 @@ +# X Layer Expert Skill + +An AI coding assistant skill that provides deep expertise for building on [X Layer](https://www.okx.com/xlayer) — OKX's Layer 2 blockchain built on OP Stack. + +Works with **Claude Code**, **Claude Desktop**, **Cursor**, **Windsurf**, **Codex CLI**, **Gemini CLI**, and any AI coding tool that supports markdown-based context files. + +## What it does + +When triggered, this skill gives your AI coding assistant specialized knowledge about: + +- **Network configuration** — RPC endpoints, chain IDs (196 mainnet / 1952 testnet), re-genesis block +- **Smart contract security** — 18 Golden Rules covering reentrancy, L2-specific risks, signature replay, oracle safety, and more +- **Contract patterns** — Hardhat & Foundry config, deploy scripts, proxy/upgrade (UUPS), contract verification via OKLink +- **Gas optimization** — OKB economics, L1 data fee structure, calldata compression +- **Bridge & cross-chain** — OP Stack predeploys, L2→L1 withdrawals, L1→L2 deposits, AggLayer +- **Flashblocks** — Sub-second pre-confirmations, reorg handling +- **OnChain Data API** — OKLink REST API with HMAC authentication for querying blocks, transactions, tokens, and event logs +- **Testing** — Mainnet forking, security testing patterns, stress testing + +## Installation + +### Quick Install (via [skills.sh](https://skills.sh)) + +```bash +npx skills add berktavsan/xlayer-expert +``` + +### Claude Code + +```bash +# Copy into skills folder +cp -r xlayer-expert ~/.claude/skills/ + +# Or clone directly +git clone https://github.com/cberktavsan/xlayer-expert.git ~/.claude/skills/xlayer-expert +``` + +### Cursor / Windsurf + +Copy `SKILL.md` and `references/` into your project root, or add them to the tool's context: + +```bash +# Add to your project as context +cp SKILL.md your-project/.cursorrules +# Or reference the files via Cursor Settings → Rules +``` + +### Claude Desktop + +Add the reference files as Project Knowledge: + +1. Open **Claude Desktop** → create or open a **Project** +2. Click **Project Knowledge** (or the 📎 icon in the project settings) +3. Upload these files: + - `SKILL.md` (main skill file with Golden Rules) + - All files from `references/` folder (`security.md`, `contract-patterns.md`, etc.) +4. Every conversation in that project will now have X Layer expertise + +> **Tip:** For the best experience, upload all 12 files. If you hit the file limit, prioritize `SKILL.md` + `security.md` + `contract-patterns.md` — these cover the most critical security and deployment patterns. + +### Codex CLI / Gemini CLI + +Include the reference files as context when starting a session: + +```bash +# Codex CLI — add to project instructions +cp SKILL.md your-project/AGENTS.md + +# Gemini CLI — add to project instructions +cp SKILL.md your-project/GEMINI.md +``` + +### Any AI Coding Tool + +The skill is plain markdown. Copy `SKILL.md` + `references/` into wherever your tool reads context files from. + +## File structure + +``` +xlayer-expert/ +├── SKILL.md # Main skill file — Golden Rules, triggers, reference guide +├── LICENSE # MIT License +├── README.md # This file +├── assets/ +│ └── x-layer.png # X Layer logo +└── references/ + ├── security.md # Solidity security rules, L2 risks, attack patterns + ├── network-config.md # RPC URLs, chain IDs, performance specs + ├── contract-patterns.md # Hardhat/Foundry config, deploy, verify, proxy + ├── token-addresses.md # Token addresses + Multicall3 + ├── l2-predeploys.md # OP Stack predeploy + L1 bridge addresses + ├── gas-optimization.md # OKB fee structure, optimization techniques + ├── testing-patterns.md # Forking, security testing, stress testing + ├── flashblocks.md # Flashblocks API, reorg risks + ├── infrastructure.md # RPC providers, xlayer-reth, monitoring, WebSocket + ├── onchain-data-api.md # OKLink REST API — blocks, txs, tokens, logs + └── zkevm-differences.md # CDK→OP Stack migration, EVM differences +``` + +## How it triggers + +In Claude Code, the skill activates automatically via triggers. For other tools, the context is available as soon as you include the files. Common trigger patterns: + +| Trigger | Examples | +|---------|----------| +| Chain IDs | `chainId: 196`, `chainId: 1952` | +| RPC URLs | `rpc.xlayer.tech`, `xlayerrpc.okx.com` | +| Tokens | OKB, WOKB, OKB as gas token | +| Infrastructure | `xlayer-reth`, flashblocks | +| Contracts | `GasPriceOracle`, `L2CrossDomainMessenger`, `OptimismPortal` | +| Tools | Hardhat/Foundry with X Layer networks | +| API | `OK-ACCESS-KEY`, `/api/v5/xlayer/`, OKLink queries | + +## Security + +Every Solidity code block written with this skill is checked against 18 Golden Rules covering: + +- Reentrancy (CEI pattern + ReentrancyGuard) +- Authentication (`msg.sender` over `tx.origin`) +- Token decimal handling (USDT=6, OKB=18) +- L2-specific risks (sequencer centralization, forced OKB sends) +- Signature safety (replay protection, malleability) +- Oracle integration (staleness checks, TWAP) +- On-chain data privacy (`private` != secret) + +## Support + +If you find this skill useful, consider supporting the project: + +**EVM Wallet:** `0x1dfcf2ac670738e74fb17c2c96da5bf333a3542c` + +## License + +MIT — see [LICENSE](LICENSE) for details. diff --git a/tools/xlayer-expert/SKILL.md b/tools/xlayer-expert/SKILL.md new file mode 100644 index 00000000..edb68b11 --- /dev/null +++ b/tools/xlayer-expert/SKILL.md @@ -0,0 +1,171 @@ +--- +name: xlayer-expert +description: "Use when code targets chainId 196 or 1952, references rpc.xlayer.tech, mentions OKB/WOKB/flashblocks/xlayer-reth, or involves Solidity contracts for X Layer (OKX L2). Also trigger for Hardhat/Foundry config with X Layer networks, bridge contracts (L2CrossDomainMessenger, OptimismPortal), GasPriceOracle predeploy, re-genesis block 42810021, or proxy/upgrade patterns. Trigger for OKLink OnChain Data API: OK-ACCESS-KEY, OK-ACCESS-SIGN, oklink, /api/v5/xlayer/. Even if 'X Layer' is not mentioned, trigger for chainId 196 or OKB as native gas token." +--- + +# X Layer Expert Skill + +## Security Golden Rules + +These rules apply to ALL Solidity code written for X Layer. Violating any of these creates exploitable vulnerabilities: + +1. **CEI Pattern** — All external calls AFTER state changes. Never send tokens/OKB before updating balances. + ```solidity + balances[msg.sender] -= amount; // Effect first + (bool ok,) = msg.sender.call{value: amount}(""); // Then interact + require(ok); + ``` + +2. **ReentrancyGuard** — Add `nonReentrant` to any function that transfers value or makes external calls. + +3. **No tx.origin auth** — ALWAYS `msg.sender`. The only acceptable `tx.origin` use is EOA check: `require(tx.origin == msg.sender)`. + +4. **Private keys** — `.env` only, NEVER hardcoded. Always validate `process.env.DEPLOYER_PRIVATE_KEY` exists. Verify `.env` is in `.gitignore`. + +5. **Token decimals** — USDT/USDC = **6**, WBTC = **8**, OKB/WETH/DAI = **18**. Use `parseUnits(amount, 6)` for USDT, NOT `parseEther()`. + +6. **Slippage + deadline** — Every swap/DEX function MUST accept `minAmountOut` + `deadline` parameters. + +7. **Ownable2Step** — Use 2-step ownership transfer to prevent accidental admin key loss. Never use basic `Ownable`. + +8. **Check call return values** — Always `require(success)` after low-level `call`/`delegatecall`. Or use OpenZeppelin `Address.functionCall()`. + +9. **Safe ERC20 approvals** — Use `forceApprove` or `safeIncreaseAllowance` (SafeERC20) instead of raw `approve()` to prevent the approval race condition. + +10. **OKB is the gas token** — NOT ETH. `msg.value` is denominated in OKB. Users must hold OKB to pay gas. Set `nativeCurrency` to OKB in chain definitions. + +11. **No unbounded loops** — Never iterate arrays that can grow without limit. Use pull patterns (users claim individually) instead of push patterns (contract distributes to all). + +12. **Signature replay protection** — Include `block.chainid` (196), `nonce`, `deadline`, and `address(this)` in all EIP-712 domain separators. + +13. **Never use transfer()/send()** — They forward only 2300 gas, which fails on contracts with logic in `receive()`. Always use `call{value: amount}("")` with return value check, or OpenZeppelin `Address.sendValue()`. + +14. **Guard receive()/fallback()** — Never write empty `receive() external payable {}`. Either emit an event for tracking or `revert()` to reject unexpected OKB. Unguarded receive permanently locks OKB in the contract. + +15. **Forced OKB sending** — `selfdestruct` (or `CREATE2` + `selfdestruct` in same tx post-EIP-6780) can force OKB into any contract, bypassing `receive()`. Never use `address(this).balance` for accounting — track deposits explicitly with a state variable. + ```solidity + // ❌ Vulnerable: attacker can inflate balance via selfdestruct + require(address(this).balance >= totalDeposits); + // ✅ Safe: track deposits explicitly + uint256 public totalDeposited; + function deposit() external payable { totalDeposited += msg.value; } + ``` + +16. **No on-chain randomness** — On L2, the sequencer controls `block.timestamp`, `block.prevrandao`, and `blockhash`. Never use these for randomness. **Chainlink VRF is NOT available on X Layer** — use commit-reveal pattern instead. See `security.md` → Randomness. + ```solidity + // ❌ Vulnerable: sequencer can predict/manipulate + uint256 random = uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao))); + // ✅ Safe: commit-reveal pattern (see security.md for full example) + ``` + +17. **Private != secret** — `private` variables are readable via `eth_getStorageAt`. Never store passwords, secret keys, or hidden game state in contract storage. Use commit-reveal or off-chain computation for sensitive data. + +18. **Signature malleability** — Raw `ecrecover` accepts both low-s and high-s values, allowing signature duplication. Always use OpenZeppelin `ECDSA.recover()` which rejects malleable signatures. Mark used signatures in a mapping to prevent replay. + ```solidity + // ❌ Vulnerable: accepts malleable signatures + address signer = ecrecover(hash, v, r, s); + // ✅ Safe: OpenZeppelin ECDSA rejects high-s values + address signer = ECDSA.recover(hash, signature); + ``` + +19. **Transient storage reentrancy** — EIP-1153 `TSTORE`/`TLOAD` costs only 100 gas — reentrancy is possible even within the 2300 gas stipend. Use `ReentrancyGuardTransient` (OZ v5.1+) for new contracts. + +20. **Input validation** — Always validate: `address != address(0)`, `amount > 0`, array length bounds, and length parity for parallel arrays. Validate at system boundaries. + +21. **Solidity ≥0.8.34** — Use 0.8.34+ as minimum compiler version. Versions 0.8.28–0.8.33 have the TSTORE Poison bug (IR pipeline corrupts transient storage cleanup). 0.8.34 fixes this. + +22. **EIP-7702 awareness** — `tx.origin == msg.sender` is no longer a reliable EOA check post-Pectra. Validate nonce, gas, and value in all signature-verified operations. See `security.md` → EIP-7702. + +## Post-Write Security Check + +After writing any Solidity code, verify ALL Golden Rules before presenting it: +- [ ] CEI pattern followed? (Rule 1) +- [ ] `nonReentrant` on all value-transfer functions? (Rule 2) +- [ ] No `tx.origin` for auth? (Rule 3) +- [ ] No hardcoded private keys? (Rule 4) +- [ ] Correct token decimals used? (Rule 5) +- [ ] Slippage + deadline on swaps? (Rule 6) +- [ ] `Ownable2Step` instead of `Ownable`? (Rule 7) +- [ ] All low-level call return values checked? (Rule 8) +- [ ] `forceApprove`/`safeIncreaseAllowance` instead of raw `approve()`? (Rule 9) +- [ ] OKB as gas token, not ETH? (Rule 10) +- [ ] No unbounded loops? (Rule 11) +- [ ] Signature replay protection (chainid, nonce, deadline)? (Rule 12) +- [ ] No `payable(x).transfer()` or `.send()` calls? (Rule 13) +- [ ] `receive()`/`fallback()` guarded or absent? (Rule 14) +- [ ] No `address(this).balance` for accounting? (Rule 15) +- [ ] No on-chain randomness (`block.timestamp`, `prevrandao`)? (Rule 16) +- [ ] No secrets stored in `private` variables? (Rule 17) +- [ ] Using `ECDSA.recover()` instead of raw `ecrecover`? (Rule 18) +- [ ] Transient storage + reentrancy considered? (Rule 19) +- [ ] Input validation at boundaries? (Rule 20) +- [ ] Solidity version ≥0.8.34? (Rule 21) +- [ ] No `tx.origin == msg.sender` as sole EOA check? (Rule 22) + +## Conditional Pattern Triggers + +When you detect these patterns in user code, automatically apply the corresponding check: + +| Pattern Detected | Auto-Check | +|-----------------|------------| +| `address(this).balance` | Warn about forced OKB sending (Rule 15) | +| `block.timestamp` or `block.prevrandao` used for randomness | Suggest commit-reveal pattern (Rule 16) — VRF not available | +| `private` variable storing password/secret/key | Warn about on-chain visibility (Rule 17) | +| `ecrecover(` | Suggest OpenZeppelin ECDSA (Rule 18) | +| `AggregatorV3Interface` or price feed | Check staleness + sequencer uptime | +| `selfdestruct` or `CREATE2` | Warn about forced send implications | +| `receive() external payable {}` (empty) | Reject — require event or revert (Rule 14) | +| `approve(` without SafeERC20 | Suggest forceApprove (Rule 9) | +| Unbounded `for` loop over storage array | Suggest pull pattern (Rule 11) | +| `tx.origin == msg.sender` as sole check | Warn about EIP-7702 bypass (Rule 22) | +| `TSTORE`/`TLOAD` without reentrancy guard | Warn about transient storage reentrancy (Rule 19) | + +## When to Trigger + +| Category | Triggers | +|----------|----------| +| **Contracts & Deploy** | `.sol` files, `hardhat.config.ts`, `foundry.toml`, `forge script/test/build`, deploy scripts | +| **Provider & Network** | `ethers.JsonRpcProvider`, `viem`, `createPublicClient`, `chainId 196/1952`, `rpc.xlayer.tech` | +| **Token & Address** | USDT/USDT0/WOKB/OKB/WETH/USDC/WBTC/DAI addresses, `Multicall3`, `aggregate3` | +| **Proxy & Upgrade** | `UUPSUpgradeable`, `TransparentUpgradeableProxy`, `initializer`, `reinitializer`, `upgradeTo` | +| **Bridge & Cross-Chain** | `L2CrossDomainMessenger`, `OptimismPortal`, `AggLayer`, bridge deposit/withdrawal | +| **Gas & Oracle** | `GasPriceOracle`, `maxFeePerGas`, `maxPriorityFeePerGas`, gas optimization | +| **OnChain Data API** | `OK-ACCESS-KEY`, `OK-ACCESS-SIGN`, `/api/v5/xlayer/`, OKLink API, block explorer queries | + +## Reference Loading Guide + +Read the reference files matching the current task. + +**MANDATORY:** Always read `security.md` before writing ANY Solidity code. No exceptions. + +| Task | Read These | +|------|-----------| +| Writing/auditing Solidity | **`security.md`** + `contract-patterns.md` | +| Deploy script or toolchain config | `contract-patterns.md` + `network-config.md` | +| Token addresses or decimals | `token-addresses.md` | +| Bridge or cross-chain code | **`security.md`** + `l2-predeploys.md` + `infrastructure.md` | +| Proxy/upgrade patterns | `contract-patterns.md` + **`security.md`** | +| Gas optimization | `gas-optimization.md` | +| Flashblocks integration | `flashblocks.md` | +| Testing or forking | `testing-patterns.md` | +| RPC setup or monitoring | `infrastructure.md` | +| On-chain data queries (REST API) | `onchain-data-api.md` | +| Pre-re-genesis / historical data | `zkevm-differences.md` | + +## Reference Files + +All under `references/`: + +| File | Content | +|------|---------| +| `network-config.md` | RPC URLs, chainId, rate limits, re-genesis, architecture | +| `token-addresses.md` | Token addresses + Multicall3 | +| `contract-patterns.md` | Hardhat + Foundry config, deploy, verify, proxy | +| `flashblocks.md` | Flashblocks API, reorg risks | +| `l2-predeploys.md` | L2 predeploy + L1 bridge addresses | +| `security.md` | Solidity security, L2 risks, attack patterns | +| `zkevm-differences.md` | CDK->OP Stack migration, EVM differences | +| `gas-optimization.md` | OKB economics, fee structure, optimization | +| `testing-patterns.md` | Forking, security testing, stress testing | +| `onchain-data-api.md` | OKLink REST API — blocks, txs, tokens, logs, HMAC auth | +| `infrastructure.md` | RPC providers, xlayer-reth, monitoring, WebSocket | diff --git a/tools/xlayer-expert/references/contract-patterns.md b/tools/xlayer-expert/references/contract-patterns.md new file mode 100644 index 00000000..f9f21be7 --- /dev/null +++ b/tools/xlayer-expert/references/contract-patterns.md @@ -0,0 +1,313 @@ +# X Layer Contract Patterns + +## Hardhat Config (TypeScript) + +```typescript +import { HardhatUserConfig } from "hardhat/config"; +import "@nomicfoundation/hardhat-toolbox"; +import '@okxweb3/hardhat-explorer-verify'; + +const config: HardhatUserConfig = { + solidity: "0.8.34", + paths: { sources: "./contracts" }, + networks: { + "xlayer-testnet": { + url: process.env.XLAYER_TESTNET_RPC_URL || "https://testrpc.xlayer.tech/terigon", + chainId: 1952, + accounts: process.env.DEPLOYER_PRIVATE_KEY ? [process.env.DEPLOYER_PRIVATE_KEY] : [], + }, + "xlayer-mainnet": { + url: process.env.XLAYER_RPC_URL || "https://rpc.xlayer.tech", + chainId: 196, + accounts: process.env.DEPLOYER_PRIVATE_KEY ? [process.env.DEPLOYER_PRIVATE_KEY] : [], + }, + }, + okxweb3explorer: { + apiKey: process.env.OKLINK_API_KEY, + }, +}; +export default config; +``` + +## Deploy +```bash +npx hardhat run scripts/deploy.ts --network xlayer-testnet +npx hardhat run scripts/deploy.ts --network xlayer-mainnet +``` + +## Contract Verification + +Standard contract: +```bash +npm install @okxweb3/hardhat-explorer-verify +npx hardhat okverify --network xlayer-mainnet +``` + +Proxy contract (UUPS/Transparent): +```bash +npx hardhat okverify --network xlayer-mainnet --contract contracts/File.sol:ContractName --proxy +``` + +Note: Wait at least 1 minute after deploy. OKLink API key: https://www.oklink.com +Note: Command name is `okverify` (without proxy) and `okverify` + `--proxy` flag (with proxy). Older docs may show `okxverify` — same command. + +Alternative etherscan plugin (chainId 196): +- apiURL: https://www.oklink.com/api/v5/explorer/contract/verify-source-code-plugin/XLAYER +- browserURL: https://www.oklink.com/xlayer + +Testnet (chainId 1952 — see `network-config.md` for chainId notes): +- apiURL: https://www.oklink.com/api/v5/explorer/contract/verify-source-code-plugin/XLAYER_TESTNET +- browserURL: https://www.oklink.com/xlayer-test + +--- + +## Foundry Config & Deploy + +### foundry.toml +```toml +[profile.default] +src = "contracts" +out = "out" +libs = ["node_modules"] +solc_version = "0.8.34" +optimizer = true +optimizer_runs = 200 +evm_version = "cancun" + +[rpc_endpoints] +xlayer = "https://rpc.xlayer.tech" +xlayer_testnet = "https://testrpc.xlayer.tech/terigon" + +[etherscan] +xlayer = { key = "${OKLINK_API_KEY}", url = "https://www.oklink.com/api/v5/explorer/contract/verify-source-code-plugin/XLAYER" } +``` + +### Foundry Deploy +```bash +forge script script/Deploy.s.sol --rpc-url xlayer --broadcast --verify +``` + +### Hardhat + Foundry Together +```bash +npm install --save-dev @nomicfoundation/hardhat-foundry +``` +```typescript +// hardhat.config.ts +import "@nomicfoundation/hardhat-foundry"; +``` + +--- + +## UUPS Proxy Pattern + +### Deploy +```solidity +// contracts/MyContractV1.sol +import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract MyContractV1 is UUPSUpgradeable, OwnableUpgradeable { + uint256 public value; + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { _disableInitializers(); } + + function initialize(uint256 _value) public initializer { + __Ownable_init(msg.sender); + __UUPSUpgradeable_init(); + value = _value; + } + + function _authorizeUpgrade(address newImpl) internal override onlyOwner {} +} +``` + +### Upgrade +```solidity +// contracts/MyContractV2.sol +contract MyContractV2 is MyContractV1 { + uint256 public newField; // New field — existing storage layout must NOT be broken! + + function reinitialize(uint256 _newField) public reinitializer(2) { + newField = _newField; + } +} +``` + +### Storage Gap Pattern +Upgradeable contracts in an inheritance chain must reserve storage slots to prevent collisions: +```solidity +contract MyContractV1 is UUPSUpgradeable, OwnableUpgradeable { + uint256 public value; + + // Reserve 49 storage slots for future variables (value uses 1 slot, total = 50) + uint256[49] private __gap; +} +``` +Without `__gap`, adding variables to a base contract shifts storage in child contracts, corrupting data. + +### Solidity Compiler Warning +> **TSTORE Poison Bug:** Solidity 0.8.28–0.8.33 have a critical bug in the IR (Yul) pipeline that corrupts transient storage cleanup (`TSTORE`/`TLOAD`). **Use 0.8.34+** which fixes this bug. Do not use `via_ir = true` with any version in the 0.8.28–0.8.33 range. + +### ERC-7201 Namespaced Storage (Recommended) +OpenZeppelin v5 best practice for proxy/upgrade safety. Replaces error-prone `__gap` arrays with deterministic storage locations: +```solidity +import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + +contract MyContractV1 is UUPSUpgradeable { + /// @custom:storage-location erc7201:myproject.storage.MyContract + struct MyContractStorage { + uint256 value; + mapping(address => uint256) balances; + } + + // keccak256(abi.encode(uint256(keccak256("myproject.storage.MyContract")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant STORAGE_LOCATION = + 0x...; // Compute with: cast keccak "myproject.storage.MyContract" + + function _getStorage() private pure returns (MyContractStorage storage $) { + assembly { $.slot := STORAGE_LOCATION } + } + + function getValue() public view returns (uint256) { + return _getStorage().value; + } +} +``` +**Why ERC-7201 over `__gap`:** +- No risk of miscounting gap size +- Storage slots are deterministic — no collision between contracts in inheritance chain +- OpenZeppelin `@custom:storage-location` annotation enables automated tooling verification +- Works alongside existing `__gap` patterns if migrating incrementally + +### Import Path: `contracts` vs `contracts-upgradeable` +- **Non-proxy contracts:** Use `@openzeppelin/contracts/...` (standard library) +- **Proxy/upgradeable contracts:** Use `@openzeppelin/contracts-upgradeable/...` (initializable variants) +- Mixing them causes subtle bugs: standard contracts have constructors that don't run behind proxies +```solidity +// ❌ Wrong: standard Ownable in an upgradeable contract — constructor won't run +import "@openzeppelin/contracts/access/Ownable.sol"; + +// ✅ Correct: upgradeable variant with initializer +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +``` + +### Critical Rules +- `constructor` must always call `_disableInitializers()` +- `initialize` function must use `initializer` modifier +- During upgrade: NEVER delete or reorder existing storage variables +- Always append new variables at the end +- Use `uint256[N] private __gap` in every upgradeable base contract +- Before upgrade: check storage layout with `@openzeppelin/upgrades-core` +- Production: use timelock + multisig for `_authorizeUpgrade`, not a single EOA + +### Contract Size +- EIP-170 limit: 24,576 bytes deployed bytecode +- Check with `forge build --sizes` or `npx hardhat compile` +- Details → `gas-optimization.md` + +--- + +## ERC721 Deploy & Verify + +### Contract +```solidity +// contracts/MyNFT.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.34; + +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; +import "@openzeppelin/contracts/access/Ownable2Step.sol"; + +contract MyNFT is ERC721, ERC721URIStorage, Ownable2Step { + uint256 private _nextTokenId; + uint256 public constant MAX_SUPPLY = 10_000; + uint256 public mintPrice = 0.01 ether; // 0.01 OKB + + constructor() ERC721("MyNFT", "MNFT") Ownable(msg.sender) {} + + function mint(string calldata uri) external payable { + require(msg.value >= mintPrice, "Insufficient OKB"); + require(_nextTokenId < MAX_SUPPLY, "Max supply reached"); + + uint256 tokenId = _nextTokenId++; + _safeMint(msg.sender, tokenId); + _setTokenURI(tokenId, uri); + } + + function withdraw() external onlyOwner { + uint256 balance = address(this).balance; + require(balance > 0, "No balance"); + (bool success,) = payable(owner()).call{value: balance}(""); + require(success, "Transfer failed"); + } + + // Required overrides + function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) { + return super.tokenURI(tokenId); + } + + function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721URIStorage) returns (bool) { + return super.supportsInterface(interfaceId); + } +} +``` + +### Deploy Script (Hardhat) +```typescript +import { ethers } from "hardhat"; + +async function main() { + if (!process.env.DEPLOYER_PRIVATE_KEY) { + throw new Error("DEPLOYER_PRIVATE_KEY env variable required"); + } + + const MyNFT = await ethers.getContractFactory("MyNFT"); + const nft = await MyNFT.deploy(); + await nft.waitForDeployment(); + console.log("MyNFT deployed to:", await nft.getAddress()); +} + +main().catch(console.error); +``` + +### Verify +```bash +npx hardhat okverify --network xlayer-mainnet +``` + +--- + +## Permit2 Approval Pattern + +Uniswap's [Permit2](https://github.com/Uniswap/permit2) provides a unified, safer token approval mechanism: +```solidity +import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; +import {ISignatureTransfer} from "permit2/src/interfaces/ISignatureTransfer.sol"; + +contract MyDEX { + IPermit2 public immutable permit2; + + constructor(IPermit2 _permit2) { + permit2 = _permit2; + } + + function swapWithPermit( + ISignatureTransfer.PermitTransferFrom calldata permit, + ISignatureTransfer.SignatureTransferDetails calldata transferDetails, + bytes calldata signature + ) external { + // Single approve to Permit2, then Permit2 handles per-tx transfers + permit2.permitTransferFrom(permit, transferDetails, msg.sender, signature); + // ... execute swap logic + } +} +``` +**Advantages over raw `approve()`:** +- Users approve Permit2 once; individual dApps get per-transaction, expiring, amount-bounded permissions via signatures +- Eliminates the approve race condition (ERC20 front-running) +- Built-in nonce and deadline enforcement +- Batch permits for multi-token operations + +**Permit2 on X Layer:** Canonical Permit2 address is `0x000000000022D473030F116dDEE9F6B43aC78BA3` (deterministic CREATE2 — same on all EVM chains). Verify deployment: call `eth_getCode` at this address. If empty (not deployed), deploy from [github.com/Uniswap/permit2](https://github.com/Uniswap/permit2) using the canonical CREATE2 deployer. diff --git a/tools/xlayer-expert/references/flashblocks.md b/tools/xlayer-expert/references/flashblocks.md new file mode 100644 index 00000000..f8ed79a6 --- /dev/null +++ b/tools/xlayer-expert/references/flashblocks.md @@ -0,0 +1,100 @@ +# X Layer Flashblocks + +200ms pre-confirmation — instant UX instead of standard 1-second blocks. + +## Architecture (xlayer-reth) +- Each 1-second X Layer block is split into **3-5 flashblocks** (~200ms intervals) +- Sequencer **streams** flashblock payloads over **WebSocket** +- RPC nodes receive this stream and build speculative in-memory pending blocks +- Flashblocks use **incremental trie cache** (~2.4-2.5x state root speedup) +- Sequence persistence: flashblocks survive node restarts + +## Endpoint +- Mainnet: https://rpc.xlayer.tech/flashblocks +- Use "pending" block tag + +## Ethers v6 + +```typescript +import { ethers } from 'ethers'; + +const provider = new ethers.JsonRpcProvider("https://rpc.xlayer.tech/flashblocks"); +const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider); + +const tx = await wallet.sendTransaction({ to: recipient, value: ethers.parseEther("0.001") }); // OKB = 18 decimals, parseEther is correct here +await tx.wait(0); // confirmations=0 = flashblock is sufficient +const balance = await provider.getBalance(address, "pending"); // Returns OKB balance (18 decimals) +``` + +## Viem + +```typescript +import { createPublicClient, http, defineChain } from 'viem'; + +const xlayer = defineChain({ + id: 196, + name: 'X Layer', + nativeCurrency: { name: 'OKB', symbol: 'OKB', decimals: 18 }, + rpcUrls: { default: { http: ['https://rpc.xlayer.tech/flashblocks'] } }, +}); + +const client = createPublicClient({ chain: xlayer, transport: http() }); +const balance = await client.getBalance({ address: "0x...", blockTag: 'pending' }); +``` + +## Supported RPC Methods (with pending tag) +eth_blockNumber, eth_call, eth_estimateGas, eth_getBalance, +eth_getTransactionCount, eth_getCode, eth_getStorageAt, +eth_getBlockByNumber, eth_getBlockByHash, eth_getTransactionReceipt, +eth_getBlockReceipts, eth_getTransactionByHash + +## Flashblocks Status Check +```typescript +// Check if flashblocks are active +const enabled = await provider.send("eth_flashblocksEnabled", []); +console.log("Flashblocks enabled:", enabled); // true | false +``` + +## When to Use +- Real-time UX (gaming, chat, social) +- High-frequency trading +- Operations requiring instant feedback + +## When NOT to Use +- Deploy scripts (standard RPC is sufficient) +- Testnet (Flashblocks not available on testnet) +- **Critical financial operations** (large transfers, settlement) — wait for finality + +## Reorg Risk Warning (CRITICAL) +- Flashblock pre-confirmation ≠ finality +- Short-lived reorgs are possible — flashblock tx may become invalid later +- Wait for at least 1 standard block confirmation before showing "transaction confirmed" to user +- **Multi-sequencer failover:** Primary sequencer (flashblocks-enabled) may fail over to backup sequencer (no flashblocks) — flashblocks may be lost in this scenario +- **Rule:** Use flashblocks for UX speed, but trust finalized blocks for state-critical logic + +## xlayer-reth Node Flashblock Configuration +To enable flashblocks on a self-hosted node: +```bash +xlayer-reth node \ + --xlayer.flashblocks-subscription \ + --xlayer.flashblocks-subscription-max-addresses 1000 \ + --flashblock-consensus \ + --flashblocks-url wss://sequencer.xlayer.tech/flashblocks +``` +- `--xlayer.sequencer-mode`: Run in sequencer mode (sequencer operators only) +- `--xlayer.flashblocks-subscription-max-addresses`: Max tracked address count + +## WebSocket Flashblock Listening +```typescript +// viem +const wsClient = createPublicClient({ + chain: xlayer, + transport: webSocket("wss://xlayerws.okx.com"), +}); + +// Get flashblock state via "pending" block tag +const balance = await wsClient.getBalance({ + address: "0x...", + blockTag: "pending", +}); +``` diff --git a/tools/xlayer-expert/references/gas-optimization.md b/tools/xlayer-expert/references/gas-optimization.md new file mode 100644 index 00000000..ef5fc56a --- /dev/null +++ b/tools/xlayer-expert/references/gas-optimization.md @@ -0,0 +1,160 @@ +# X Layer Gas Optimization + +## OKB Gas Token Economics + +- **Native gas token:** OKB (NOT ETH!) +- **Fixed supply:** 21 million OKB (fully unlocked after August 2025) +- **Average tx cost:** ~$0.0005 USD +- Users must hold OKB balance — gas cannot be paid with ETH or stablecoins +- OKB has 18 decimals (as native token) + +## Fee Structure (Multi-Component) + +X Layer transaction fee = **L2 execution fee** + **L1 data fee** + +| Component | Description | Variability | +|---|---|---| +| L2 execution fee | L2 computation cost | Low | +| L1 data fee | Cost of posting calldata to L1 | **High** (depends on L1 gas price) | + +- L1 gas spikes → directly increase L2 costs +- Cost may vary between batches + +## GasPriceOracle Predeploy + +Address: `0x420000000000000000000000000000000000000f` + +Provides L1 base fee and overhead information: +```solidity +interface IGasPriceOracle { + function l1BaseFee() external view returns (uint256); + function gasPrice() external view returns (uint256); + function baseFee() external view returns (uint256); +} + +// Usage +IGasPriceOracle oracle = IGasPriceOracle(0x420000000000000000000000000000000000000F); +uint256 l1Fee = oracle.l1BaseFee(); +``` + +To get dynamic gas price: +```typescript +const gasPrice = await provider.send("eth_gasPrice", []); +``` + +## EIP-1559 Support + +X Layer supports EIP-1559: +```typescript +// ethers v6 +const feeData = await provider.getFeeData(); +const tx = await wallet.sendTransaction({ + to: recipient, + value: ethers.parseEther("0.01"), + maxFeePerGas: feeData.maxFeePerGas, + maxPriorityFeePerGas: feeData.maxPriorityFeePerGas, +}); + +// viem +const request = await walletClient.prepareTransactionRequest({ + to: recipient, + value: parseEther("0.01"), + // viem automatically calculates EIP-1559 parameters +}); +``` + +- Base fee is burned (deflationary pressure) +- Priority fee goes to sequencer (validator) +- Prefer `type: 2` (EIP-1559) transactions over `type: 0` (legacy) + +## Optimization Strategies + +### Contract Size (EIP-170) +- Limit: **24,576 bytes** (24 KB) deployed bytecode +- Deploy reverts if exceeded +- Check: `npx hardhat compile` → look at artifact size +- Foundry: `forge build --sizes` + +Reduction techniques: +1. Libraries for code sharing (external library → DELEGATECALL) +2. Custom errors instead of string messages (`error InsufficientBalance()`) +3. Internal functions are inlined — make them external if too large +4. Diamond pattern (EIP-2535) as a last resort + +### Compiler Optimizer +```json +{ + "solidity": { + "version": "0.8.34", + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + } + } + } +} +``` +- `runs: 200` — general purpose, deploy + runtime balance +- `runs: 1` — cheap deploy, expensive runtime (rarely called contracts) +- `runs: 10000` — expensive deploy, cheap runtime (frequently called contracts) + +### Multicall3 +Address: `0xcA11bde05977b3631167028862bE2a173976CA11` + +Batch multiple read calls into a single transaction: +```typescript +import { Contract } from "ethers"; + +const multicall = new Contract("0xcA11bde05977b3631167028862bE2a173976CA11", [ + "function aggregate3(tuple(address target, bool allowFailure, bytes callData)[] calls) view returns (tuple(bool success, bytes returnData)[])" +], provider); + +const calls = [ + { target: tokenAddr, allowFailure: false, callData: erc20.interface.encodeFunctionData("balanceOf", [user]) }, + { target: tokenAddr, allowFailure: false, callData: erc20.interface.encodeFunctionData("totalSupply") }, +]; +const results = await multicall.aggregate3(calls); +``` + +### Storage Optimization +```solidity +// ❌ 3 storage slots (96 bytes) +uint256 a; // slot 0 +uint256 b; // slot 1 +uint256 c; // slot 2 + +// ✅ 1 storage slot (32 bytes) — packed +uint128 a; // slot 0 (first 16 bytes) +uint64 b; // slot 0 (next 8 bytes) +uint64 c; // slot 0 (last 8 bytes) +``` +- SSTORE (20,000 gas cold / 5,000 warm) is the most expensive opcode +- Variables in the same slot only need one SSTORE when written together +- Mappings and dynamic arrays always use separate slots + +### Calldata vs Memory +```solidity +// ✅ Read-only parameters: calldata (cheap) +function process(bytes calldata data) external { ... } + +// Memory: when modification is needed +function modify(bytes memory data) internal { ... } +``` + +### Events vs Storage +```solidity +// ❌ Expensive: write to storage +mapping(uint256 => string) public logs; +function log(uint256 id, string memory msg) external { + logs[id] = msg; // ~20,000+ gas +} + +// ✅ Cheap: emit event +event LogEntry(uint256 indexed id, string message); +function log(uint256 id, string memory msg) external { + emit LogEntry(id, msg); // ~375 + 8*len gas +} +``` +- Events cannot be read on-chain (only via off-chain indexers) +- Use events for audit trails, activity logs diff --git a/tools/xlayer-expert/references/infrastructure.md b/tools/xlayer-expert/references/infrastructure.md new file mode 100644 index 00000000..f7e36afb --- /dev/null +++ b/tools/xlayer-expert/references/infrastructure.md @@ -0,0 +1,165 @@ +# X Layer Infrastructure + +## Public vs Dedicated RPC + +| | Public | Dedicated | +|---|---|---| +| Rate Limit | 100 req/sec | Unlimited/Enterprise | +| Uptime SLA | None | 99.9–99.99% | +| DDoS Protection | Basic | Advanced | +| Archive Data | Limited | Full | +| Cost | Free | $5–50/month | +| Use Case | Test/prototype | Production | + +### Provider List +| Provider | Features | +|---|---| +| QuickNode | Re-genesis aware (`x-qn-height` header), add-on marketplace | +| Blockdaemon | Enterprise grade, dedicated node | +| Getblock | Shared/dedicated options | +| ZAN | OKX ecosystem integration | +| Chainstack | Elastic node, archive data | +| Unifra | Free tier available | +| BlockPI | Global distributed | + +--- + +## Self-hosted RPC (xlayer-toolkit) + +### One-Click Setup +```bash +mkdir -p /data/xlayer-mainnet && cd /data/xlayer-mainnet +curl -fsSL https://raw.githubusercontent.com/okx/xlayer-toolkit/main/rpc-setup/one-click-setup.sh -o one-click-setup.sh +chmod +x one-click-setup.sh && ./one-click-setup.sh +``` + +### Configuration +- Execution client: **Geth** (stable) or **Reth** (performant, Rust-based) +- Runs via Docker containerization +- Port mapping: + - `8545` — HTTP RPC + - `8546` — WebSocket + - `8551` — Engine API (requires JWT auth) + - `30303` — P2P +- Disk: ~500 GB+ (archive), ~100 GB (pruned) +- RAM: 16 GB minimum, 32 GB recommended +- CPU: 4+ cores + +### xlayer-reth (Recommended — High Performance) +Reth v1.11.0-based, with X Layer-specific optimizations: +```bash +# Docker +docker run -d --name xlayer-reth \ + -p 8545:8545 -p 8546:8546 -p 30303:30303 \ + -v /data/xlayer:/data \ + okx/xlayer-reth:latest \ + node \ + --http --http.port 8545 \ + --ws --ws.port 8546 \ + --datadir /data \ + --chain xlayer-mainnet +``` + +#### X Layer-Specific CLI Arguments +```bash +# Flashblocks subscription (RPC node) +--xlayer.flashblocks-subscription +--xlayer.flashblocks-subscription-max-addresses 1000 + +# Sequencer mode (sequencer operators only) +--xlayer.sequencer-mode + +# Flashblock consensus (build blocks from WebSocket stream) +--flashblock-consensus +--flashblocks-url wss://sequencer.xlayer.tech/flashblocks + +# Legacy RPC routing (for pre-re-genesis blocks) +--rpc.legacy-url https://legacy-erigon.xlayer.tech +--rpc.legacy-timeout 30s +``` + +#### Crate Structure (source code reference) +| Crate | Description | +|---|---| +| xlayer-flashblocks | Flashblock lifecycle (cache, consensus, pubsub, validation) | +| xlayer-rpc | Custom RPC extensions (`eth_flashblocksEnabled`) | +| xlayer-builder | Block building (sequencer) | +| xlayer-chainspec | Chain spec (mainnet/testnet/devnet genesis, Jovian hardfork) | +| xlayer-monitor | Event subscription monitoring | +| xlayer-legacy-rpc | Block height-based legacy routing | +| Source: [github.com/okx/xlayer-reth](https://github.com/okx/xlayer-reth) | + +### JWT Authentication (Engine API) +```bash +# Generate JWT secret +openssl rand -hex 32 > /data/xlayer-mainnet/jwt.hex + +# Start Geth +geth --authrpc.jwtsecret=/data/xlayer-mainnet/jwt.hex \ + --http --http.port 8545 --ws --ws.port 8546 + +# Start Reth +xlayer-reth node --authrpc.jwtsecret=/data/xlayer-mainnet/jwt.hex \ + --http --http.port 8545 --ws --ws.port 8546 +``` + +### Multi-Sequencer Architecture +- Primary sequencer: flashblocks-enabled (xlayer-reth sequencer mode) +- Backup sequencer: flashblocks-less fallback (Conductor cluster for failover) +- 99.9%+ uptime target +- During failover, flashblock stream is interrupted, falls back to standard 1s blocks + +### Security Audit +- January 2026: xlayer-reth diff audit report available (`security-reviews/` directory) +- Comprehensive Reth fork change review + +--- + +## Monitoring Stack + +### Monitoring Tools +- **Prometheus + Grafana**: Node metrics (port 6060 → Prometheus scrape, Grafana dashboards on port 3000) +- **xlayer-trace-monitor** (`xlayer-toolkit/trace-monitor`): Transaction/block lifecycle tracking, outputs CSV +- **Tenderly**: Smart contract monitoring, alerting, transaction simulation (mainnet fork), gas profiling +- **Alchemy Monitor**: dApp analytics, webhook alerts on contract events + +--- + +## WebSocket Subscriptions + +- Endpoint: `wss://xlayerws.okx.com` (primary) or `wss://ws.xlayer.tech` (alternative) +- Supported events: `block`, `pending`, `logs` (contract events) +- ethers v6: `new ethers.WebSocketProvider("wss://xlayerws.okx.com")` +- viem: `createPublicClient({ transport: webSocket("wss://xlayerws.okx.com") })` + +### Mandatory Rules +1. **Automatic reconnection** — WS connections can drop at any time; implement reconnect with backoff +2. **Chain reorg handling** — short-lived reorgs are possible; listen for removed logs +3. **Heartbeat/ping** — detect stale connections before they cause missed events +4. **Backpressure** — prevent event queue overflow at high throughput + +--- + +## AggLayer & Cross-Chain + +> **Note:** X Layer now runs on OP Stack. AggLayer integration continues but the primary bridge mechanism uses OP Stack's standard bridge. + +### OP Stack Bridge (Current — Hybrid) +- `L1→L2` deposit: via OptimismPortal, ~minutes +- `L2→L1` withdrawal: OP Stack standard ~7 day challenge period (PermissionedDisputeGame) +- **AggLayer ZK proof integration** provides faster finality path: `aggsender` submits certificate → `agglayer-prover` generates ZK proof → proof submitted to L1 +- Standard OP Stack bridge contracts (addresses in l2-predeploys.md) +- OKX front-end bridge deprecated August 15, 2025 — use community bridges or contracts directly + +### AggLayer (Additional Layer) +- Pessimistic proof mechanism: assumes all chains are **insecure** +- Each chain's proof is independently verified +- Unified bridge vision: cross-chain transfer without wrapped tokens + +### AggLayer v0.3+ +- Non-CDK chain support (Ethereum L1, other L2s) +- Cross-chain atomic swap potential +- Shared liquidity layer vision + +### Cross-Chain Message Pattern +For L2→L1 message sending code example, see `l2-predeploys.md` → Cross-Chain Message Pattern. diff --git a/tools/xlayer-expert/references/l2-predeploys.md b/tools/xlayer-expert/references/l2-predeploys.md new file mode 100644 index 00000000..269c2492 --- /dev/null +++ b/tools/xlayer-expert/references/l2-predeploys.md @@ -0,0 +1,113 @@ +# X Layer Predeploy & Bridge Addresses + +## L2 Predeploy Addresses (Mainnet & Testnet — same) + +| Contract | Address | +|------------------------|---------------------------------------------| +| L2CrossDomainMessenger | 0x4200000000000000000000000000000000000007 | +| GasPriceOracle | 0x420000000000000000000000000000000000000f | +| L1Block | 0x4200000000000000000000000000000000000015 | +| L2ToL1MessagePasser | 0x4200000000000000000000000000000000000016 | +| SequencerFeeVault | 0x4200000000000000000000000000000000000011 | +| BaseFeeVault | 0x4200000000000000000000000000000000000019 | +| L1FeeVault | 0x420000000000000000000000000000000000001a | +| ProxyAdmin | 0x4200000000000000000000000000000000000018 | + +## L1 Bridge Contracts (Ethereum Mainnet) + +> **Verification note:** These addresses were sourced from X Layer documentation. Before relying on them in production, verify on [Etherscan](https://etherscan.io) by checking that each address has the expected contract name and is associated with X Layer / OKX deployment. + +| Contract | Address | +|------------------------|---------------------------------------------| +| OptimismPortal | 0x64057ad1DdAc804d0D26A7275b193D9DACa19993 | +| L1CrossDomainMessenger | 0xF94B553F3602a03931e5D10CaB343C0968D793e3 | +| SystemConfig | 0x5065809Af286321a05fBF85713B5D5De7C8f0433 | +| DisputeGameFactory | 0x9D4c8FAEadDdDeeE1Ed0c92dAbAD815c2484f675 | + +## L1 Bridge Contracts (Sepolia Testnet) + +| Contract | Address | +|------------------------|---------------------------------------------| +| OptimismPortal | 0x1529a34331D7d85C8868Fc88EC730aE56d3Ec9c0 | +| L1CrossDomainMessenger | 0xEf40d5432D37B3935a11710c73F395e2c9921295 | +| SystemConfig | 0x06BE4b4A9a28fF8EED6da09447Bc5DAA676efac3 | +| DisputeGameFactory | 0x80388586ab4580936BCb409Cc2dC6BC0221e1B6F | + +## Important Notes (OP Stack — Current Architecture) +- **L2→L1 withdrawal:** OP Stack standard ~7 day challenge period (PermissionedDisputeGame active), but AggLayer ZK proofs can provide faster finality +- **Soft finality:** ~1 second (sequencer confirmation), ~200ms with flashblocks pre-confirmation +- **Front-end bridge:** OKX web bridge deprecated August 15, 2025 — use community bridges or contract calls directly +- FaultDisputeGame: Not deployed (0x0000...) — **PermissionedDisputeGame active** (only authorized participants can open disputes) +- Bridge stages: deposit → L2 execute → challenge period → L1 finality +- GasPriceOracle (`0x420...000f`): provides L1 fee info — for gas optimization see → `gas-optimization.md` +- Predeploy addresses are standard OP Stack addresses (`0x4200...` prefix) + +## Cross-Chain Message Pattern +```solidity +// L2 → L1 message sending +IL2CrossDomainMessenger messenger = IL2CrossDomainMessenger( + 0x4200000000000000000000000000000000000007 +); + +messenger.sendMessage( + l1TargetAddress, // Target contract on L1 + abi.encodeCall(ITarget.handleMessage, (data)), + 200_000 // L1 gas limit +); +``` + +## L1 → L2 Deposit Pattern +```solidity +// On L1 (Ethereum Mainnet): deposit OKB/ETH to X Layer via OptimismPortal +interface IOptimismPortal { + function depositTransaction( + address _to, + uint256 _value, + uint64 _gasLimit, + bool _isCreation, + bytes calldata _data + ) external payable; +} + +// Simple L1 → L2 OKB deposit +IOptimismPortal portal = IOptimismPortal(0x64057ad1DdAc804d0D26A7275b193D9DACa19993); // Mainnet + +portal.depositTransaction{value: msg.value}( + recipientOnL2, // Target address on X Layer + msg.value, // Amount to deposit + 100_000, // L2 gas limit + false, // Not a contract creation + "" // No calldata (simple transfer) +); +``` + +```typescript +// TypeScript: L1 → L2 deposit via ethers.js +import { ethers } from "ethers"; + +const OPTIMISM_PORTAL = "0x64057ad1DdAc804d0D26A7275b193D9DACa19993"; // Mainnet +const PORTAL_ABI = [ + "function depositTransaction(address _to, uint256 _value, uint64 _gasLimit, bool _isCreation, bytes _data) payable" +]; + +const l1Provider = new ethers.JsonRpcProvider(process.env.ETH_RPC_URL); +const wallet = new ethers.Wallet(process.env.DEPLOYER_PRIVATE_KEY!, l1Provider); +const portal = new ethers.Contract(OPTIMISM_PORTAL, PORTAL_ABI, wallet); + +const tx = await portal.depositTransaction( + recipientOnL2, + ethers.parseEther("1.0"), // 1 OKB + 100_000n, // L2 gas limit + false, // Not contract creation + "0x", // No calldata + { value: ethers.parseEther("1.0") } +); +await tx.wait(); +// Deposit typically arrives on L2 within minutes +``` + +## AggLayer +- X Layer continues AggLayer integration (on top of OP Stack) +- Unified bridge vision: cross-chain transfer without wrapped tokens +- AggLayer v0.3+: non-CDK chain support +- Details → `infrastructure.md` diff --git a/tools/xlayer-expert/references/network-config.md b/tools/xlayer-expert/references/network-config.md new file mode 100644 index 00000000..3f75c928 --- /dev/null +++ b/tools/xlayer-expert/references/network-config.md @@ -0,0 +1,51 @@ +# X Layer Network Configuration + +## Architecture +- **Current:** OP Stack-based optimistic rollup (migrated from Polygon CDK on October 27, 2025) +- **Execution client:** Geth or Reth (xlayer-reth) +- **Custom hardfork:** Jovian (mainnet: 2025-12-02 16:00:01 UTC, testnet: 2025-11-28 11:00:00 UTC) +- Re-genesis block 42,810,021 = CDK → OP Stack transition point + +## Mainnet (chainId: 196 / 0xC4) +- RPC: https://rpc.xlayer.tech +- RPC (alt): https://xlayerrpc.okx.com +- Flashblocks RPC: https://rpc.xlayer.tech/flashblocks +- WSS: wss://xlayerws.okx.com +- WSS (alt): wss://ws.xlayer.tech +- Explorer: https://www.okx.com/web3/explorer/xlayer +- Gas Token: OKB (native) + +## Testnet (chainId: 1952 / 0x7A0) +- RPC: https://testrpc.xlayer.tech +- RPC (terigon): https://testrpc.xlayer.tech/terigon +- RPC (alt): https://xlayertestrpc.okx.com/terigon +- Explorer: https://www.oklink.com/x-layer-testnet +- Gas Station: GET https://testrpc.xlayer.tech/terigon/gasstation +- **WARNING:** ChainId 195 is WRONG (legacy CDK era). ALWAYS use **1952** for testnet. Source: xlayer-reth chainspec. Never use 195 — it will fail to connect. + +## Performance +- Block time: ~1 second (faster than standard OP Stack 2-second blocks) +- TPS: 5,000 +- Rate limit: 100 req/sec per IP (public RPC) + +## Re-genesis (CRITICAL) +- Mainnet re-genesis at block **42,810,021** (October 27, 2025) +- Testnet re-genesis at block **12,241,700** +- Pre-re-genesis data requires a different node type (cdk-Erigon) +- Details → `zkevm-differences.md` + +## EIP-1559 Support +- `maxFeePerGas` and `maxPriorityFeePerGas` are supported +- Prefer `type: 2` transactions over legacy +- Details → `gas-optimization.md` + +## WebSocket Subscriptions +- WSS endpoint: `wss://xlayerws.okx.com` (primary) or `wss://ws.xlayer.tech` (alternative) +- Supported events: `block`, `pending`, `logs` +- Automatic reconnection is mandatory +- Details → `infrastructure.md` +> **Note:** Official documentation also lists `wss://xlayerws.okx.com` and `wss://ws.xlayer.tech`. If one endpoint is unreachable, try the alternatives. + +## Infrastructure Providers (Dedicated RPC) +QuickNode, Blockdaemon, Getblock, ZAN, Chainstack, Unifra, BlockPI +- Details → `infrastructure.md` diff --git a/tools/xlayer-expert/references/onchain-data-api.md b/tools/xlayer-expert/references/onchain-data-api.md new file mode 100644 index 00000000..3bd13f11 --- /dev/null +++ b/tools/xlayer-expert/references/onchain-data-api.md @@ -0,0 +1,266 @@ +# X Layer OnChain Data API (OKLink) + +REST API for querying X Layer blockchain data — blocks, transactions, addresses, tokens, event logs, and contract verification. Alternative to subgraph/indexer for most data queries. + +## Base URL & Authentication + +**Base URL:** `https://web3.okx.com` + +**API Key Portal:** https://web3.okx.com/xlayer/dev-portal (connect wallet → create API key with passphrase) + +### Required Headers (every request) + +| Header | Value | +|--------|-------| +| `OK-ACCESS-KEY` | Your API key | +| `OK-ACCESS-SIGN` | HMAC SHA256 signature (Base64) | +| `OK-ACCESS-TIMESTAMP` | UTC timestamp (ISO format) | +| `OK-ACCESS-PASSPHRASE` | Passphrase from key creation | +| `Content-Type` | `application/json` (POST only) | + +### HMAC Signing + +``` +sign = Base64( HmacSHA256( timestamp + METHOD + requestPath + body, SecretKey ) ) +``` + +- `timestamp` must match `OK-ACCESS-TIMESTAMP` header exactly +- `METHOD` is uppercase (`GET`, `POST`) +- For GET: query params are part of `requestPath`; body is empty string +- For POST: body is raw JSON string +- Server time difference must not exceed **30 seconds** + +### TypeScript Example + +```typescript +import crypto from "crypto"; + +function createOKXHeaders( + method: "GET" | "POST", + requestPath: string, + body: string = "", + apiKey: string, + secretKey: string, + passphrase: string +) { + const timestamp = new Date().toISOString().slice(0, -5) + "Z"; + const message = timestamp + method + requestPath + body; + const sign = crypto + .createHmac("sha256", secretKey) + .update(message) + .digest("base64"); + + return { + "OK-ACCESS-KEY": apiKey, + "OK-ACCESS-SIGN": sign, + "OK-ACCESS-TIMESTAMP": timestamp, + "OK-ACCESS-PASSPHRASE": passphrase, + "Content-Type": "application/json", + }; +} + +// Usage +const path = "/api/v5/xlayer/block/block-fills?chainShortName=XLAYER&height=50000000"; +const headers = createOKXHeaders("GET", path, "", API_KEY, SECRET_KEY, PASSPHRASE); +const res = await fetch(`https://web3.okx.com${path}`, { headers }); +const data = await res.json(); // { code: "0", msg: "", data: [...] } +``` + +### Response Format + +```json +{ + "code": "0", + "msg": "", + "data": [{ /* result objects */ }] +} +``` +`"code": "0"` = success. Any other code = error. + +### Error Handling + +Always check both HTTP status and API response code: +```typescript +async function fetchOKX(method: "GET" | "POST", path: string, body?: string): Promise { + const headers = createOKXHeaders(method, path, body ?? "", API_KEY, SECRET_KEY, PASSPHRASE); + const res = await fetch(`https://web3.okx.com${path}`, { + method, + headers, + ...(body ? { body } : {}), + }); + + if (!res.ok) { + throw new Error(`OKLink HTTP error: ${res.status} ${res.statusText}`); + } + + const json = await res.json(); + + if (json.code !== "0") { + throw new Error(`OKLink API error ${json.code}: ${json.msg}`); + } + + if (!json.data || json.data.length === 0) { + return []; // No results — not an error + } + + return json.data as T[]; +} +``` + +Common error codes: +| Code | Meaning | +|------|---------| +| `"50000"` | Body required / invalid parameters | +| `"50001"` | Service temporarily unavailable | +| `"50005"` | API key / signature invalid | +| `"50011"` | Rate limit exceeded | +| `"50014"` | Timestamp expired (>30s drift) | + +--- + +## Limits & Constraints + +| Constraint | Value | +|------------|-------| +| Pagination default | 20 per page | +| Pagination max | 50-100 (varies by endpoint) | +| Max results per query | ~10,000 (list endpoints) | +| Max results (logs) | ~1,000 | +| Block range (multi queries) | Max 10,000 block difference | +| Batch queries | Max 20 addresses or tx hashes | +| Time filter range | Max 1 year | +| `chainShortName` value | `XLAYER` (required on all endpoints) | + +--- + +## Endpoint Reference + +### Block Data (4 endpoints) + +| Endpoint | Description | +|----------|-------------| +| `GET /api/v5/xlayer/block/block-fills` | Block details by height (`height` param) | +| `GET /api/v5/xlayer/block/block-list` | Paginated block list (max 100/page) | +| `GET /api/v5/xlayer/block/transaction-list` | Transactions in a block; filter by `protocolType` (`transaction`, `internal`, `token_20`, `token_721`, `token_1155`) | +| `GET /api/v5/xlayer/block/transaction-list-multi` | Batch transactions across block range (`startBlockHeight` → `endBlockHeight`) | + +### Address Data (9 endpoints) + +| Endpoint | Description | +|----------|-------------| +| `GET .../address/information-evm` | Balance, tx count, contract status, first/last tx time | +| `GET .../address/token-balance` | Token holdings by `protocolType` (`token_20`, `token_721`, `token_1155`); includes USD value | +| `GET .../address/transaction-list` | All tx types; **L2 fields: `challengeStatus`, `l1OriginHash`** | +| `GET .../address/normal-transaction-list` | Standard txs only; has `gasPrice`, `gasUsed`, `transactionType` fields | +| `GET .../address/internal-transaction-list` | Internal txs; has `operation` field (`call`, `staticcall`, etc.) | +| `GET .../address/token-transaction-list` | Token transfers by `protocolType`; filter by `tokenContractAddress` | +| `GET .../address/normal-transaction-list-multi` | Batch: up to 20 addresses, max 10k block range | +| `GET .../address/internal-transaction-list-multi` | Batch internal txs: up to 20 addresses | +| `GET .../address/native-token-position-list` | OKB rich list — top 10,000 holders with rank | + +### Transaction Data (9 endpoints) + +| Endpoint | Description | +|----------|-------------| +| `GET .../transaction/transaction-list` | Chain tx list; filter by `blockHash` or `height` | +| `GET .../transaction/large-transaction-list` | High-value txs (min 100 OKB threshold via `type` param) | +| `GET .../transaction/unconfirmed-transaction-list` | Pending/mempool transactions | +| `GET .../transaction/internal-transaction-detail` | Internal txs for single `txId` | +| `GET .../transaction/token-transaction-detail` | Token transfers within single `txId` | +| `GET .../transaction/transaction-fills` | Full tx details; batch up to 20 `txid`s (comma-separated) | +| `GET .../transaction/transaction-multi` | Same as fills; batch up to 20 | +| `GET .../transaction/internal-transaction-multi` | Internal txs for up to 20 hashes | +| `GET .../transaction/token-transfer-multi` | Token transfers for up to 20 hashes | + +**Transaction types:** `"0"` (legacy), `"1"` (EIP-2930), `"2"` (EIP-1559) +**States:** `"success"`, `"fail"`, `"pending"` + +### Token Data (3 endpoints) + +| Endpoint | Description | +|----------|-------------| +| `GET .../token/token-list` | Token info + market data; sort by `totalMarketCap` or `transactionAmount24h` | +| `GET .../token/position-list` | Token holder ranking (top 10k); includes `positionChange24h` | +| `GET .../token/transaction-list` | Token transfers; filter by `minAmount`/`maxAmount` | + +**Protocol types:** `token_20` (ERC20), `token_721` (ERC721), `token_1155` (ERC1155), `token_10` (ERC10) + +### Event Log Data (4 endpoints) + +| Endpoint | Description | +|----------|-------------| +| `GET .../log/by-block-and-address` | Logs by block range + contract address | +| `GET .../log/by-address-and-topic` | Logs by address + `topic0` filter | +| `GET .../log/by-address` | All logs for contract address | +| `GET .../log/by-transaction` | Logs emitted in single tx | + +Log response: `height`, `address`, `topics[]`, `data`, `methodId`, `blockHash`, `txId`, `logIndex` +Max ~1,000 results per query. + +### Event Log Limitations + +- Only `topic0` filtering is supported — no `topic1`/`topic2`/`topic3` filters +- To filter by specific sender/receiver in Transfer events, fetch by `topic0` and filter client-side +- Max ~1,000 results per query; use block range pagination for larger datasets + +### Contract Verification (5 endpoints) + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `.../contract/verify` | POST | Submit source code for verification | +| `.../contract/verify-result` | GET | Check verification status (`0`=pending, `1`=success, `2`=failed) | +| `.../contract/verify-proxy` | POST | Verify proxy contract — `proxyType`: `1`=Transparent, `2`=UUPS, `3`=Beacon | +| `.../contract/verify-proxy-result` | GET | Check proxy verification status | +| `.../contract/get-verified-contract` | GET | Get verified ABI + source code | + +**Verify POST params:** `sourceCode`, `contractAddress`, `compilerVersion`, `runs`, `constructorArguments`, `licenseType`, `evmVersion` + +--- + +## L2-Specific Response Fields + +The address transaction list endpoint returns OP Stack-specific fields: + +| Field | Description | +|-------|-------------| +| `challengeStatus` | Challenge period status for optimistic rollup transactions | +| `l1OriginHash` | Corresponding L1 transaction hash | +| `isAaTransaction` | Whether tx uses account abstraction (ERC-4337) | + +--- + +## Common Patterns + +### Get address balance + token holdings +```typescript +// 1. Native OKB balance +const info = await fetchOKX("GET", "/api/v5/xlayer/address/information-evm?chainShortName=XLAYER&address=0x..."); +console.log(info.data[0].balance); // OKB balance + +// 2. ERC20 token balances +const tokens = await fetchOKX("GET", "/api/v5/xlayer/address/token-balance?chainShortName=XLAYER&address=0x...&protocolType=token_20"); +tokens.data[0].tokenList.forEach(t => console.log(t.symbol, t.holdingAmount, t.valueUsd)); +``` + +### Monitor large transactions +```typescript +const large = await fetchOKX("GET", "/api/v5/xlayer/transaction/large-transaction-list?chainShortName=XLAYER&type=1000"); +// Returns txs with amount >= 1000 OKB +``` + +### Query event logs by topic +```typescript +// Find all Transfer events for a token +const transferTopic = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"; +const logs = await fetchOKX("GET", + `/api/v5/xlayer/log/by-address-and-topic?chainShortName=XLAYER&address=${tokenAddr}&topic0=${transferTopic}` +); +``` + +### Get token holder ranking +```typescript +const holders = await fetchOKX("GET", + `/api/v5/xlayer/token/position-list?chainShortName=XLAYER&tokenContractAddress=${usdtAddr}&limit=50` +); +holders.data[0].positionList.forEach(h => console.log(h.rank, h.holderAddress, h.amount)); +``` diff --git a/tools/xlayer-expert/references/security.md b/tools/xlayer-expert/references/security.md new file mode 100644 index 00000000..2bce6da2 --- /dev/null +++ b/tools/xlayer-expert/references/security.md @@ -0,0 +1,678 @@ +# X Layer Security Reference + +## Solidity Security Rules + +### CEI (Checks-Effects-Interactions) Pattern +All external calls MUST happen AFTER state changes: +```solidity +// ✅ Correct: CEI pattern +function withdraw(uint256 amount) external { + require(balances[msg.sender] >= amount, "Insufficient"); // Check + balances[msg.sender] -= amount; // Effect + (bool ok, ) = msg.sender.call{value: amount}(""); // Interaction + require(ok, "Transfer failed"); +} + +// ❌ Wrong: Interaction before Effect — reentrancy vulnerability +function withdraw(uint256 amount) external { + require(balances[msg.sender] >= amount, "Insufficient"); + (bool ok, ) = msg.sender.call{value: amount}(""); // External call BEFORE state update! + balances[msg.sender] -= amount; +} +``` + +### ReentrancyGuard +Mandatory for bridge contracts and token transfer functions: +```solidity +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; + +contract Bridge is ReentrancyGuard { + function bridgeToken(uint256 amount) external nonReentrant { + // ... + } +} +``` + +#### Transient Storage Reentrancy (EIP-1153) +> **Warning:** With EIP-1153 (transient storage), the 2300 gas stipend from `transfer()`/`send()` is NO longer a reentrancy barrier. `TSTORE`/`TLOAD` cost only 100 gas each, making reentrancy possible even within 2300 gas. (Source: ChainSecurity, 2025) + +For new contracts, prefer `ReentrancyGuardTransient` (OpenZeppelin v5.1+) which uses transient storage for cheaper reentrancy protection: +```solidity +// Gas-efficient reentrancy guard using transient storage (EIP-1153) +import "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol"; + +contract MyContract is ReentrancyGuardTransient { + function withdraw(uint256 amount) external nonReentrant { + // TSTORE-based lock — cheaper than SSTORE, auto-clears at tx end + } +} +``` +**Key difference:** Classic `ReentrancyGuard` uses `SSTORE` (~5000 gas). `ReentrancyGuardTransient` uses `TSTORE` (~100 gas) and auto-resets at transaction end — no need to reset the lock manually. + +### Authentication +- NEVER use `tx.origin` for authentication — always use `msg.sender` +- `tx.origin` is only acceptable for checking "is the caller an EOA?" +```solidity +// ❌ Dangerous: Vulnerable to phishing attacks +require(tx.origin == owner); + +// ✅ Correct +require(msg.sender == owner); +``` + +#### EIP-7702 Delegated Execution Security +> **Critical (Pectra upgrade, 2025):** EIP-7702 allows EOAs to delegate execution to a contract via `SET_CODE_TX_TYPE (0x04)`. Phishing attacks using EIP-7702 are already occurring on Ethereum mainnet. X Layer cross-chain users are at risk. + +**Threats:** +- Attacker tricks user into signing a delegation that transfers their assets +- Delegated code can bypass `tx.origin == msg.sender` EOA checks +- Nonce manipulation via delegated execution + +**Protections:** +```solidity +// ❌ No longer safe as sole EOA check (EIP-7702 breaks this assumption) +require(tx.origin == msg.sender, "Not EOA"); + +// ✅ Additional validation for EIP-7702 awareness +function _validateCaller() internal view { + require(msg.sender == tx.origin, "Not EOA"); + // If your contract handles delegated calls, also validate: + require(msg.value == 0 || msg.value == expectedValue, "Unexpected value"); + // Check nonce consistency if implementing meta-tx +} +``` +- Validate `nonce`, `gas`, and `value` in all signature-verified operations +- Never trust `tx.origin` alone — always combine with `msg.sender` checks and application-level authorization +- Warn users about blind-signing risks in your dApp UI + +### Access Control +- Simple ownership: OpenZeppelin `Ownable2Step` (2-step transfer, prevents accidental loss) +- Role-based: OpenZeppelin `AccessControl` or `AccessControlEnumerable` +- Critical functions: `onlyOwner` + `timelock` combination + +#### Role Separation Best Practices (OWASP 2025 #1) +Access control is the #1 vulnerability category (OWASP Smart Contract Top 10, 2025). Apply defense-in-depth: +```solidity +import "@openzeppelin/contracts/access/AccessControlEnumerable.sol"; + +contract Treasury is AccessControlEnumerable { + bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); + bytes32 public constant GUARDIAN_ROLE = keccak256("GUARDIAN_ROLE"); + + // Separate roles: operator can execute, guardian can pause/cancel + function executeTransfer(address to, uint256 amount) external onlyRole(OPERATOR_ROLE) { + // ... + } + + function emergencyPause() external onlyRole(GUARDIAN_ROLE) { + _pause(); + } +} +``` +- **Enumerate roles:** Use `AccessControlEnumerable` to audit who has which role +- **Timelock critical operations:** Admin functions (upgrade, large transfers) should go through `TimelockController` +- **Multi-sig for admin:** Never use a single EOA for `DEFAULT_ADMIN_ROLE` in production +- **Revoke default admin:** After setup, consider renouncing `DEFAULT_ADMIN_ROLE` from deployer and transferring to a timelock/multisig + +### Integer Overflow/Underflow +- Solidity 0.8+ has automatic checks +- Only use `unchecked` blocks for gas optimization when safety is guaranteed +```solidity +// Safe: i can never overflow (bounded loop) +for (uint256 i = 0; i < arr.length;) { + // ... + unchecked { ++i; } +} +``` + +#### Downcast Safety +Solidity 0.8+ checks arithmetic overflow/underflow, but **downcasting silently truncates** without reverting: +```solidity +// ❌ VULNERABLE: silently truncates — no revert on overflow +uint256 big = type(uint256).max; +uint128 small = uint128(big); // Silently truncated to type(uint128).max + +// ✅ Safe: use OpenZeppelin SafeCast — reverts on overflow +import "@openzeppelin/contracts/utils/math/SafeCast.sol"; +using SafeCast for uint256; +uint128 safe = big.toUint128(); // Reverts: "SafeCast: value doesn't fit in 128 bits" +``` + +### Front-running Protection +- Commit-reveal pattern: first commit hash, then reveal value +- Private mempool: via Flashblocks or dedicated RPC +- Time-based nonce or deadline parameters + +#### MEV on X Layer +- **Single sequencer (OKX):** No public mempool MEV like Ethereum — users cannot run MEV bots targeting the mempool +- **Flashblocks:** Create a ~200ms visibility window before finalization — see `flashblocks.md` for reorg risks +- **Sequencer ordering:** The sequencer determines transaction ordering; no PBS (proposer-builder separation) yet +- **Always use slippage protection** (Rule 6) regardless of sequencer ordering guarantees — sequencer trust assumptions may change + +### Slippage Protection +Mandatory parameters for DEX/swap operations: +```solidity +function swap( + uint256 amountIn, + uint256 minAmountOut, // Slippage protection + uint256 deadline // Time protection +) external { + require(block.timestamp <= deadline, "Expired"); + uint256 amountOut = _calculateSwap(amountIn); + require(amountOut >= minAmountOut, "Slippage exceeded"); + // ... +} +``` + +### Flash Loan Attack Surfaces +- Oracle manipulation: use TWAP instead of spot price +- Price impact: large single-block trades manipulating price +- Protection: Chainlink price feed (if available for the pair) or multi-block TWAP, liquidity checks + +### Oracle / Price Feed Guidance +- **Chainlink on X Layer:** Limited availability — only major pairs (OKB/USD, ETH/USD, BTC/USD, USDT/USD). Check [Chainlink X Layer feeds](https://docs.chain.link/data-feeds/price-feeds/addresses?network=xlayer) before depending on a feed. +- For pairs WITHOUT Chainlink feeds: use DEX TWAP (e.g., Uniswap V3 `observe()`) with multi-block averaging as fallback +- Always validate feed freshness: `require(block.timestamp - updatedAt < MAX_STALENESS)` +- Always check `answer > 0` and `answeredInRound >= roundId` + +### ERC20 Approve Race Condition +The `approve()` function is vulnerable to front-running — an attacker can spend the old allowance before the new one takes effect: +```solidity +// ❌ Vulnerable: attacker front-runs and spends old + new allowance +token.approve(spender, newAmount); + +// ✅ Recommended: use SafeERC20 (handles the race condition internally) +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +using SafeERC20 for IERC20; + +token.forceApprove(spender, amount); // OZ v5+ (best) +token.safeIncreaseAllowance(spender, amount); // OZ v4 + +// Legacy fallback (if SafeERC20 is unavailable): reset to 0 first +token.approve(spender, 0); +token.approve(spender, newAmount); +``` +For new DEX/DeFi integrations, consider **Permit2** which eliminates the approve race entirely — see `contract-patterns.md` → Permit2 Approval Pattern. + +### transfer() / send() 2300 Gas Limit +`transfer()` and `send()` forward only 2300 gas — not enough for contracts with logic in `receive()`: +```solidity +// ❌ Dangerous: 2300 gas limit — fails on contracts with receive() logic +payable(recipient).transfer(amount); +bool ok = payable(recipient).send(amount); + +// ✅ Correct: call{value:} forwards all available gas +(bool success,) = payable(recipient).call{value: amount}(""); +require(success, "Transfer failed"); + +// ✅ Better: OpenZeppelin Address +Address.sendValue(payable(recipient), amount); +``` + +### receive() / fallback() Protection +Unguarded receive functions permanently lock OKB in the contract: +```solidity +// ❌ Dangerous: OKB sent to this contract is locked forever +receive() external payable {} + +// ✅ Option 1: emit event for tracking +receive() external payable { + emit Received(msg.sender, msg.value); +} + +// ✅ Option 2: reject unexpected OKB +receive() external payable { + revert("Direct OKB transfers not accepted"); +} +``` + +### Cross-Function Reentrancy +`nonReentrant` on one function does NOT protect other functions that share the same state: +```solidity +// ❌ Vulnerable: withdraw() has nonReentrant, but transfer() does not +function withdraw(uint256 amount) external nonReentrant { + require(balances[msg.sender] >= amount); + balances[msg.sender] -= amount; // Effect (CEI ✓) + (bool ok,) = msg.sender.call{value: amount}(""); // Interaction + require(ok); + // During callback, attacker calls transfer() which has NO nonReentrant +} + +function transfer(address to, uint256 amount) external { + // No nonReentrant! Attacker re-enters here during withdraw callback + require(balances[msg.sender] >= amount); + balances[msg.sender] -= amount; + balances[to] += amount; +} +``` +Protection: use `nonReentrant` modifier on ALL public functions that read or write shared state, not just the one with the external call. + +### Multicall Reentrancy +Batched calls via Multicall3 (`0xcA11bde05977b3631167028862bE2a173976CA11`) execute in a single transaction but bypass `nonReentrant` between calls in the batch: +- `nonReentrant` won't protect between calls within the same Multicall batch +- `msg.value` is shared across all calls in the batch — do NOT use `msg.value` in payable functions called via Multicall (double-spending risk) +- If inheriting OpenZeppelin `Multicall`: mark payable functions `nonReentrant` AND validate `msg.value` is consumed exactly once + +### ERC-4626 Vault Inflation Attack +First depositor can manipulate share price by donating tokens directly to the vault: +``` +Attack scenario: +1. Attacker deposits 1 wei → gets 1 share +2. Attacker donates 1,000,000 tokens directly to vault (transfer, not deposit) +3. Victim deposits 999,000 tokens: shares = 999,000 * 1 / 1,000,000 = 0 (rounded down) +4. Victim gets 0 shares — attacker redeems 1 share for all ~2M tokens +``` +```solidity +// ✅ Protection: OpenZeppelin ERC4626 _decimalsOffset() adds virtual offset +// to prevent rounding manipulation. Use OZ implementation directly: +import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; + +contract MyVault is ERC4626 { + constructor(IERC20 asset) ERC4626(asset) ERC20("Vault", "vTKN") {} + + // OZ default: _decimalsOffset() returns 0 + // Override to add protection (recommended): + function _decimalsOffset() internal pure override returns (uint8) { + return 3; // Adds 1000 virtual shares — prevents inflation attack + } +} +``` + +### Unchecked Return Values +Low-level calls that ignore return values silently fail, potentially losing funds: +```solidity +// ❌ Dangerous: return value ignored — transfer may fail silently +payable(recipient).send(amount); +address(target).call(data); + +// ✅ Correct: check return value +(bool success, ) = payable(recipient).call{value: amount}(""); +require(success, "Transfer failed"); + +// ✅ Better: use OpenZeppelin Address library +import "@openzeppelin/contracts/utils/Address.sol"; +Address.sendValue(payable(recipient), amount); +Address.functionCall(target, data); +``` + +### Delegatecall Risks +Especially relevant for proxy/upgrade patterns on X Layer: +- `delegatecall` executes code in the **caller's storage context** — a malicious implementation can overwrite proxy storage +- Never `delegatecall` to untrusted or unverified contracts +- Proxy and implementation storage layouts MUST be identical — adding/reordering variables in upgrades breaks storage +- Always call `_disableInitializers()` in implementation constructors to prevent direct initialization +- Use OpenZeppelin's `StorageSlot` for unstructured storage patterns +- For upgrade-safe storage, prefer **ERC-7201 namespaced storage** over `__gap` arrays — see `contract-patterns.md` → ERC-7201 Namespaced Storage + +### Denial of Service (DoS) via Unbounded Loops +Iterating over arrays that can grow without bound will eventually exceed the block gas limit: +```solidity +// ❌ Dangerous: unbounded loop + .transfer() (see Rule 13) +function distributeRewards() external { + for (uint256 i = 0; i < recipients.length; i++) { + payable(recipients[i]).transfer(rewards[i]); // Bad: unbounded + 2300 gas limit + } +} + +// ✅ Safe: pull pattern — each recipient claims their own reward +mapping(address => uint256) public pendingRewards; + +function claimReward() external nonReentrant { + uint256 reward = pendingRewards[msg.sender]; + require(reward > 0, "No reward"); + pendingRewards[msg.sender] = 0; // Effect + (bool success,) = payable(msg.sender).call{value: reward}(""); // Interaction + require(success, "Transfer failed"); +} +``` +Also: a single failed `transfer` in a loop reverts the entire batch — another reason to prefer pull patterns. + +### Signature Replay Protection +Off-chain signatures (EIP-712, permit) must include replay protection fields: +```solidity +// EIP-712 domain separator MUST include all of these: +bytes32 public DOMAIN_SEPARATOR = keccak256(abi.encode( + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + keccak256(bytes("MyContract")), + keccak256(bytes("1")), + block.chainid, // 196 for X Layer mainnet — prevents cross-chain replay + address(this) // prevents cross-contract replay +)); + +// Per-user nonce — prevents same-chain replay +mapping(address => uint256) public nonces; + +// Deadline — prevents stale signatures from being used indefinitely +function executeWithSignature( + bytes calldata sig, + uint256 deadline, + uint256 nonce +) external { + require(block.timestamp <= deadline, "Signature expired"); + require(nonce == nonces[msg.sender]++, "Invalid nonce"); + // ... verify signature against DOMAIN_SEPARATOR +} +``` +Note: `block.chainid` returns 196 on X Layer mainnet, 1952 on testnet. + +### Input Validation Checklist +Apply these checks at all public/external function boundaries: +```solidity +// Address validation +require(recipient != address(0), "Zero address"); +require(recipient != address(this), "Self-transfer"); + +// Amount validation +require(amount > 0, "Zero amount"); +require(amount <= MAX_TRANSFER, "Exceeds limit"); + +// Array bounds +require(recipients.length > 0 && recipients.length <= MAX_BATCH, "Invalid batch size"); +require(recipients.length == amounts.length, "Length mismatch"); + +// String/bytes length +require(bytes(name).length > 0 && bytes(name).length <= 32, "Invalid name"); +``` +Rule of thumb: validate at system boundaries (user input, external calls), trust internal functions. + +### ERC-4337 Account Abstraction Security +> X Layer supports account abstraction (`isAaTransaction` field in OKLink API). Smart account wallets introduce unique attack surfaces. + +**Key Threats:** +1. **UserOp gas field manipulation:** Attacker submits UserOp with inflated `preVerificationGas` or `callGasLimit` to drain paymaster funds +2. **Paymaster exploitation:** Malicious UserOp tricks paymaster into paying for operations it shouldn't sponsor +3. **Signature validation bypass:** `validateUserOp` must verify signature + nonce atomically +4. **Storage access rules:** `validateUserOp` can only access the account's own associated storage (ERC-7562 rules) + +**Protection patterns:** +```solidity +// In your smart account's validateUserOp: +function validateUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash, + uint256 missingAccountFunds +) external returns (uint256 validationData) { + // 1. Verify signature (MUST check — never skip) + require(_validateSignature(userOp, userOpHash), "Invalid signature"); + + // 2. Validate nonce (prevents replay) + // EntryPoint manages nonces, but validate key-space if using 2D nonces + + // 3. Pay prefund if needed + if (missingAccountFunds > 0) { + (bool ok,) = payable(msg.sender).call{value: missingAccountFunds}(""); + // NOTE: Intentionally ignoring return value per ERC-4337 spec (exception to Rule 8). + // EntryPoint will revert entire UserOp if underfunded — checking `ok` here is unnecessary. + (ok); // Silence unused variable warning + } + + return 0; // 0 = valid (no time restriction). Packed as (authorizer, validUntil, validAfter) +} +``` +- **Paymaster validation:** Always cap `maxCost` and validate the `paymasterAndData` field. Set spending limits per user/timeframe. +- **Bundler trust:** Do not assume the bundler is honest — validate all UserOp fields on-chain. + +--- + +## L2-Specific Security + +### Sequencer Centralization +- X Layer sequencer is controlled by OKX (multi-sequencer cluster with failover) +- Sequencer can censor transactions (delay/skip) +- Consider L1 forced inclusion mechanism for critical functions +- Transactions cannot be sent during sequencer downtime + +### block.timestamp Manipulation +- Sequencer determines `block.timestamp` (with ±drift) +- Time-sensitive operations (auction, vesting, deadline) must account for this drift +- `block.number` is less reliable on L2 — use L1 block reference (`L1Block` predeploy) + +### L2→L1 Withdrawal Security +- Withdrawal period: ~7 days challenge period (PermissionedDisputeGame active on OP Stack), but AggLayer ZK proofs can provide faster finality +- Proof generation time is variable +- Withdrawal replay protection: each withdrawal has a unique nonce +- Sufficient balance check in bridge contract is mandatory + +### Bridge Reentrancy Risks +- ERC-777 tokens can create reentrancy via `tokensReceived` hook +- Fee-on-transfer tokens (deflationary) transfer less than expected amount +- Rebase tokens create balance inconsistencies in bridge +- Protection: token whitelist + ReentrancyGuard + actual balance delta check + +### Message Replay Protection +- Cross-chain messages must validate `nonce + sourceChainId + destinationChainId` +- Prevent same message from executing on multiple chains +- L2CrossDomainMessenger provides these protections built-in + +### OP Stack Specific Security +- **PermissionedDisputeGame:** Only authorized participants can open disputes — not yet permissionless +- **Multi-sequencer:** OKX operates with backup failover for 99.9%+ uptime +- **Bridge security:** OP Stack standard bridge contracts, extensively audited +- **Security audit:** January 2026 xlayer-reth diff audit report available + +--- + +## [LEGACY] zkEVM Circuit Risks (Pre-Re-genesis Only) + +> **IMPORTANT:** The following risks apply ONLY to the old Polygon CDK/zkEVM era (block ≤42,810,020). Post-re-genesis X Layer runs on standard EVM-compatible OP Stack. + +### Opcode Circuit Bugs (Historical) +- SHL/SHR opcodes previously caused circuit bugs (PSE/Scroll audits) +- Formal verification found 6 soundness + 1 completeness issues + +### Precompile Circuit Capacity (Historical) +- In old zkEVM, each precompile had fixed capacity in the circuit +- Post-re-genesis: standard EVM precompile behavior — this limitation no longer applies + +--- + +## Private Key Management + +### Mandatory Rules +1. `DEPLOYER_PRIVATE_KEY` only in `.env` file +2. `.env` file MUST be in `.gitignore` (verify!) +3. Hardcoded private key = critical security vulnerability +4. `process.env` check mandatory in deploy scripts: + +```typescript +// deploy.ts start +if (!process.env.DEPLOYER_PRIVATE_KEY) { + throw new Error("DEPLOYER_PRIVATE_KEY env variable required"); +} +``` + +### Production Environment +- Use hardware wallet (Ledger) or multi-sig (Safe) +- Gnosis Safe: https://safe.global — multi-sig wallet +- Deployer key and admin key should be separate +- Add timelock contract for admin operations + +--- + +## Additional Security Patterns + +### Forced OKB Sending (selfdestruct bypass) +> **Post-EIP-6780 (Dencun):** `selfdestruct` on pre-existing contracts only sends balance — it does NOT destroy code or storage. The only case where `selfdestruct` fully destroys a contract is when called in the **same transaction** as `CREATE2` deployment. This `CREATE2` + `selfdestruct` in same tx still works as a full forced-send vector. + +`selfdestruct` (or `CREATE2` + `selfdestruct` in the same transaction) can force OKB into any contract, bypassing `receive()` guards: +```solidity +// ❌ VULNERABLE: attacker uses selfdestruct to inflate address(this).balance +contract Vault { + uint256 public totalDeposits; + + function deposit() external payable { + totalDeposits += msg.value; + } + + function isBalanceCorrect() public view returns (bool) { + return address(this).balance == totalDeposits; // Can be broken! + } +} + +// ✅ Safe: track deposits with state variable, ignore forced sends +contract SafeVault { + uint256 public totalDeposited; + + function deposit() external payable { + totalDeposited += msg.value; + } + + function availableBalance() public view returns (uint256) { + return totalDeposited; // Not address(this).balance + } +} +``` + +### Randomness (VRF) +> **Chainlink VRF is NOT available on X Layer as of March 2026.** Do not use VRF code examples targeting X Layer — they will not work. + +On L2, the sequencer controls `block.timestamp`, `block.prevrandao`, and `blockhash`. These MUST NOT be used for randomness: +```solidity +// ❌ VULNERABLE: sequencer can predict/manipulate all of these +uint256 bad1 = uint256(keccak256(abi.encodePacked(block.timestamp))); +uint256 bad2 = uint256(keccak256(abi.encodePacked(block.prevrandao))); +uint256 bad3 = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1)))); +``` + +Use **commit-reveal** as the primary on-chain randomness pattern: +```solidity +// Phase 1: Commit (user submits hash) +mapping(address => bytes32) public commitments; +mapping(address => uint256) public commitBlock; + +function commit(bytes32 hash) external { + commitments[msg.sender] = hash; + commitBlock[msg.sender] = block.number; +} + +// Phase 2: Reveal (after N blocks, user reveals secret) +function reveal(bytes32 secret) external { + require(commitments[msg.sender] == keccak256(abi.encodePacked(secret)), "Invalid reveal"); + require(block.number > commitBlock[msg.sender] + REVEAL_DELAY, "Too early"); + require(block.number <= commitBlock[msg.sender] + REVEAL_DEADLINE, "Too late"); + + // Combine user secret with future blockhash for randomness + uint256 random = uint256(keccak256(abi.encodePacked( + secret, + blockhash(commitBlock[msg.sender] + REVEAL_DELAY) + ))); + + delete commitments[msg.sender]; + // Use `random` ... +} +``` +**Limitations:** `blockhash()` only works for the last 256 blocks. Set `REVEAL_DELAY` and `REVEAL_DEADLINE` accordingly. + +> **⚠ VRF Import Guidance — Future Reference Only.** VRF is NOT available on X Layer. Do NOT import these for X Layer deployments. When VRF becomes available, use stable import paths (NOT `/dev/`): +> ```solidity +> // Correct (stable): +> import {VRFConsumerBaseV2Plus} from "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2Plus.sol"; +> // Wrong (development — do NOT use): +> // import {VRFConsumerBaseV2Plus} from "@chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol"; +> ``` + +### On-Chain Data Privacy +`private` variables are NOT hidden — anyone can read them via `eth_getStorageAt`: +```solidity +// ❌ VULNERABLE: "private" does not mean secret +contract BadGame { + uint256 private secretNumber = 42; // Readable by anyone! + bytes32 private password = keccak256("hunter2"); // Readable by anyone! +} + +// Reading private storage (off-chain): +// slot 0: await provider.getStorage(contractAddr, 0) → secretNumber +// slot 1: await provider.getStorage(contractAddr, 1) → password hash + +// ✅ Safe: use commit-reveal for hidden values +contract SafeGame { + mapping(address => bytes32) public commitments; + + function commit(bytes32 hash) external { + commitments[msg.sender] = hash; // hash = keccak256(abi.encodePacked(value, salt)) + } + + function reveal(uint256 value, bytes32 salt) external { + require(commitments[msg.sender] == keccak256(abi.encodePacked(value, salt)), "Bad reveal"); + delete commitments[msg.sender]; + // ... use value + } +} +``` + +### Signature Malleability (ECDSA s-value) +ECDSA signatures accept both low-s and high-s values for the same message. This means an attacker can compute a second valid signature from any existing one: +```solidity +// ❌ VULNERABLE: accepts malleable signatures — attacker can forge second valid sig +function verify(bytes32 hash, uint8 v, bytes32 r, bytes32 s) public pure returns (address) { + return ecrecover(hash, v, r, s); +} + +// ✅ Safe: OpenZeppelin ECDSA rejects high-s values automatically +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; + +function verify(bytes32 hash, bytes calldata signature) public pure returns (address) { + return ECDSA.recover(MessageHashUtils.toEthSignedMessageHash(hash), signature); +} + +// Also: always track used signatures to prevent replay +mapping(bytes32 => bool) public usedSignatures; + +function executeWithSig(bytes32 hash, bytes calldata sig) external { + bytes32 sigHash = keccak256(sig); + require(!usedSignatures[sigHash], "Signature already used"); + usedSignatures[sigHash] = true; + + address signer = ECDSA.recover(MessageHashUtils.toEthSignedMessageHash(hash), sig); + require(signer == authorizedSigner, "Invalid signer"); + // ... execute action +} +``` + +### Oracle Integration (Chainlink Price Feed) +When using price oracles, always check for staleness and L2 sequencer uptime: +```solidity +import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; + +contract PriceConsumer { + AggregatorV3Interface internal priceFeed; + uint256 public constant STALENESS_THRESHOLD = 3600; // 1 hour + + constructor(address feedAddress) { + priceFeed = AggregatorV3Interface(feedAddress); + } + + function getLatestPrice() public view returns (int256 price, uint8 decimals) { + ( + uint80 roundId, + int256 answer, + /* uint256 startedAt */, + uint256 updatedAt, + uint80 answeredInRound + ) = priceFeed.latestRoundData(); + + // Staleness check + require(block.timestamp - updatedAt <= STALENESS_THRESHOLD, "Stale price data"); + // Round completeness check + require(answeredInRound >= roundId, "Stale round"); + // Sanity check + require(answer > 0, "Invalid price"); + + return (answer, priceFeed.decimals()); + } +} + +// L2 Sequencer Uptime Feed +// Chainlink L2 Sequencer Uptime Feed: NOT confirmed available on X Layer (March 2026). +// Check https://docs.chain.link/data-feeds/l2-sequencer-feeds for updates. +// When available, add this check before using any Chainlink price feed: +// +// AggregatorV3Interface sequencerUptimeFeed = AggregatorV3Interface(SEQUENCER_FEED_ADDR); +// (, int256 answer,, uint256 startedAt,) = sequencerUptimeFeed.latestRoundData(); +// bool isSequencerUp = answer == 0; +// require(isSequencerUp, "Sequencer is down"); +// uint256 timeSinceUp = block.timestamp - startedAt; +// require(timeSinceUp > GRACE_PERIOD, "Grace period not over"); +``` +> **Note:** Check Chainlink's official page for available price feeds on X Layer (chainId 196). If Chainlink feeds are not available for a specific pair, consider using TWAP from a DEX with sufficient liquidity. diff --git a/tools/xlayer-expert/references/testing-patterns.md b/tools/xlayer-expert/references/testing-patterns.md new file mode 100644 index 00000000..32095ba1 --- /dev/null +++ b/tools/xlayer-expert/references/testing-patterns.md @@ -0,0 +1,322 @@ +# X Layer Testing Patterns + +## Hardhat Mainnet Forking + +Test against real mainnet state: +```bash +npx hardhat node --fork https://rpc.xlayer.tech --fork-block-number +``` + +### Hardhat Config +```typescript +// hardhat.config.ts +networks: { + hardhat: { + forking: { + url: "https://rpc.xlayer.tech", + blockNumber: 50_000_000, // Fixed block — deterministic tests + }, + chainId: 196, + }, +} +``` + +### Test Helpers +```typescript +import { ethers } from "hardhat"; +import { loadFixture, time, mine } from "@nomicfoundation/hardhat-network-helpers"; + +// Impersonate a wealthy account +await ethers.provider.send("hardhat_impersonateAccount", [richAddress]); +const signer = await ethers.getSigner(richAddress); + +// Set balance +await ethers.provider.send("hardhat_setBalance", [ + address, + ethers.toQuantity(ethers.parseEther("100")) +]); + +// Advance time +await time.increase(3600); // 1 hour + +// Mine blocks +await mine(10); // 10 blocks +``` + +### Testing with Real Contracts +```typescript +it("should swap on DEX", async () => { + const router = await ethers.getContractAt("IUniswapV2Router", DEX_ROUTER_ADDRESS); + const usdt = await ethers.getContractAt("IERC20", "0x1E4a5963aBFD975d8c9021ce480b42188849D41d"); + + // Impersonate a USDT holder + await ethers.provider.send("hardhat_impersonateAccount", [usdtWhale]); + const whale = await ethers.getSigner(usdtWhale); + + // Approve & swap + await usdt.connect(whale).approve(router.target, amount); + // ... test swap logic +}); +``` + +--- + +## Foundry Forking + +```bash +forge test --fork-url https://rpc.xlayer.tech --fork-block-number 50000000 -vvv +``` + +For `foundry.toml` config, see `contract-patterns.md` → Foundry Config section. + +### Foundry Test Cheatcodes +```solidity +// test/MyContract.t.sol +pragma solidity ^0.8.34; +import "forge-std/Test.sol"; +import "../contracts/MyContract.sol"; + +contract MyContractTest is Test { + MyContract public target; + + function setUp() public { + target = new MyContract(); + } + + function testWithFork() public { + // Impersonate + vm.prank(someAddress); + + // Set balance (OKB — native) + vm.deal(someAddress, 100 ether); + + // Warp time + vm.warp(block.timestamp + 1 hours); + + // Roll block number + vm.roll(block.number + 100); + + // Expect revert + vm.expectRevert("Insufficient balance"); + target.withdraw(1000); + + // Expect event + vm.expectEmit(true, true, false, true); + emit Transfer(from, to, amount); + target.transfer(to, amount); + } + + // Fuzz testing + function testFuzz_deposit(uint256 amount) public { + vm.assume(amount > 0 && amount < 1e24); + target.deposit{value: amount}(); + assertEq(target.balanceOf(address(this)), amount); + } +} +``` + +### Foundry Deploy Script +```solidity +// script/Deploy.s.sol +pragma solidity ^0.8.34; +import "forge-std/Script.sol"; +import "../contracts/MyContract.sol"; + +contract DeployScript is Script { + function run() external { + uint256 deployerKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); + vm.startBroadcast(deployerKey); + + MyContract c = new MyContract(); + console.log("Deployed:", address(c)); + + vm.stopBroadcast(); + } +} +``` +```bash +forge script script/Deploy.s.sol --rpc-url xlayer --broadcast --verify +``` + +--- + +## Hardhat + Foundry Together + +```bash +npm install --save-dev @nomicfoundation/hardhat-foundry +``` +```typescript +// hardhat.config.ts +import "@nomicfoundation/hardhat-foundry"; +``` +- Use Hardhat for deploy + verify +- Use Foundry for fast testing + fuzzing +- They share the same `contracts/` directory + +--- + +## Stress Testing (xlayer-toolkit) + +### Adventure Tool +20,000 concurrent accounts with ERC20 + native transfers: +```bash +git clone https://github.com/okx/xlayer-toolkit +cd xlayer-toolkit/adventure +# Edit config file +# Strategy: "erc20", "native", "mixed" +go run . --strategy erc20 --accounts 20000 --rpc https://rpc.xlayer.tech +``` + +### Transaction Replayor +Replay existing transactions for stress testing: +```bash +cd xlayer-toolkit/replayor +# Replay transactions from a specific block range +go run . --from-block 50000000 --to-block 50001000 --rpc https://rpc.xlayer.tech +``` + +--- + +## Security Testing Patterns + +### Reentrancy Attack Simulation +```solidity +// test/ReentrancyTest.t.sol +contract MaliciousReceiver { + IVault public target; + uint256 public attackCount; + + constructor(address _target) { + target = IVault(_target); + } + + function attack() external payable { + target.deposit{value: msg.value}(); + target.withdraw(msg.value); + } + + receive() external payable { + if (address(target).balance >= msg.value && attackCount < 5) { + attackCount++; + target.withdraw(msg.value); + } + } +} + +contract ReentrancyTest is Test { + function testReentrancyBlocked() public { + MaliciousReceiver attacker = new MaliciousReceiver(address(vault)); + vm.deal(address(attacker), 1 ether); + vm.expectRevert(); // Must revert if nonReentrant is applied + attacker.attack(); + } +} +``` + +### Access Control Bypass Testing +```solidity +function testUnauthorizedAccessReverts() public { + address unauthorized = makeAddr("hacker"); + + vm.startPrank(unauthorized); + vm.expectRevert(); // or vm.expectRevert(abi.encodeWithSelector(OwnableUnauthorizedAccount.selector, unauthorized)) + target.adminOnlyFunction(); + vm.stopPrank(); +} + +function testOwnershipTransfer() public { + address newOwner = makeAddr("newOwner"); + // Step 1: current owner initiates transfer + target.transferOwnership(newOwner); + // Step 2: new owner must accept (Ownable2Step) + vm.prank(newOwner); + target.acceptOwnership(); + assertEq(target.owner(), newOwner); +} +``` + +### Invariant Testing (Foundry) +Invariant tests run random sequences of function calls and assert properties that must always hold: +```solidity +// test/invariants/TokenInvariant.t.sol +contract TokenInvariantTest is Test { + MyToken public token; + Handler public handler; + + function setUp() public { + token = new MyToken(); + handler = new Handler(token); + targetContract(address(handler)); + } + + // This invariant is checked after every random call sequence + function invariant_totalSupplyMatchesBalances() public view { + assertEq( + token.totalSupply(), + token.balanceOf(address(handler)) + token.balanceOf(address(this)) + ); + } + + function invariant_noUnauthorizedMints() public view { + assertLe(token.totalSupply(), MAX_SUPPLY); + } +} + +// Handler: defines which functions the fuzzer can call +contract Handler is Test { + MyToken public token; + constructor(MyToken _token) { token = _token; } + + function deposit(uint256 amount) external { + amount = bound(amount, 1, 100 ether); + deal(address(this), amount); + token.deposit{value: amount}(); + } + + function withdraw(uint256 amount) external { + amount = bound(amount, 0, token.balanceOf(address(this))); + token.withdraw(amount); + } +} +``` +Run invariant tests: `forge test --mt invariant -vvv` + +### Flash Loan Attack Simulation +```solidity +function testFlashLoanPriceManipulation() public { + // 1. Record price before attack + uint256 priceBefore = oracle.getPrice(tokenA); + + // 2. Simulate a large swap (flash loan style) + vm.deal(address(this), 10000 ether); + router.swapExactTokensForTokens(largeAmount, 0, path, address(this), block.timestamp); + + // 3. Verify oracle is NOT manipulated (if using TWAP) + uint256 priceAfter = oracle.getPrice(tokenA); + assertApproxEqRel(priceBefore, priceAfter, 0.05e18); // Within 5% +} +``` + +### Security Analysis Tools +- **Slither** (static analysis): `slither . --solc-remaps '@openzeppelin=node_modules/@openzeppelin'` +- **Mythril** (symbolic execution): `myth analyze contracts/MyContract.sol` +- **Halmos** (formal verification for Foundry): `halmos --contract MyContract` +- **HEVM** (symbolic EVM): `hevm test --match testSymbolic` +- Run Slither in CI pipeline — catches most common vulnerability patterns automatically + +--- + +## Pre-Deploy Test Checklist + +Verify before every deployment: + +- [ ] **Unit tests** — All functions tested (Hardhat or Foundry) +- [ ] **Integration tests** — Tested with real contracts via mainnet fork +- [ ] **Gas benchmarks** — `hardhat-gas-reporter` or `forge test --gas-report` +- [ ] **Decimals validation** — USDT/USDC: 6, OKB/WETH/DAI: 18, WBTC: 8 +- [ ] **Upgrade path** — Proxy contracts: initialize + upgrade tested +- [ ] **Re-genesis boundary** — Block 42,810,021 edge case handled +- [ ] **Fuzz testing** — Critical functions tested with fuzzer +- [ ] **Slippage/deadline** — DEX operations have protection parameters +- [ ] **Access control** — Authorized functions have correct modifiers +- [ ] **Event emission** — All state changes emit events diff --git a/tools/xlayer-expert/references/token-addresses.md b/tools/xlayer-expert/references/token-addresses.md new file mode 100644 index 00000000..cbdd168c --- /dev/null +++ b/tools/xlayer-expert/references/token-addresses.md @@ -0,0 +1,41 @@ +# X Layer Token Addresses + +## Mainnet + +| Token | Address | Decimals | Note | +|----------|---------------------------------------------|----------|------| +| **USDT0** | 0x779Ded0c9e1022225f8E0630b35a9b54bE713736 | 6 | Primary USDT — Stargate/LayerZero native | +| USDT | 0x1E4a5963aBFD975d8c9021ce480b42188849D41d | 6 | Legacy/bridged — prefer USDT0 for new integrations | +| WOKB | 0xe538905cf8410324e03A5A23C1c177a474D59b2b | 18 | +| WETH | 0x5A77f1443D16ee5761d310e38b62f77f726bC71c | 18 | +| USDC | 0x74b7F16337b8972027F6196A17a631aC6dE26d22 | 6 | +| USDC.e | 0xA8CE8aee21bC2A48a5EF670afCc9274C7bbbC035 | 6 | +| WBTC | 0xEA034fb02eB1808C2cc3adbC15f447B93CbE08e1 | 8 | +| DAI | 0xC5015b9d9161Dca7e18e32f6f25C4aD850731Fd4 | 18 | + +## Testnet + +| Token | Address | Decimals | +|--------|---------------------------------------------|----------| +| WETH | 0xBec7859BC3d0603BeC454F7194173E36BF2Aa5C8 | 18 | +| USDT | Deploy via MockERC20.sol — no fixed address | | + +## Utility Contracts + +| Contract | Address | Note | +|------------|---------------------------------------------|-------------------| +| Multicall3 | 0xcA11bde05977b3631167028862bE2a173976CA11 | Batch read calls | + +## OKB (Native Gas Token) +- OKB is X Layer's native gas token (like ETH on Ethereum) +- Decimals: 18 (as native token) +- Fixed supply: 21 million OKB (fully unlocked after August 2025) +- Gas can only be paid in OKB + +## Testnet Faucet +- OKX testnet faucet: https://www.okx.com/xlayer/faucet +- Requires Sepolia ETH (bridge to get testnet OKB) + +## Notes +- Common decimals mistake: use `parseUnits(amount, 6)` for USDT/USDC, NOT `parseEther` +- WBTC uses 8 decimals — different from most tokens, be careful! diff --git a/tools/xlayer-expert/references/zkevm-differences.md b/tools/xlayer-expert/references/zkevm-differences.md new file mode 100644 index 00000000..9bc2f256 --- /dev/null +++ b/tools/xlayer-expert/references/zkevm-differences.md @@ -0,0 +1,165 @@ +# X Layer EVM Differences & Architecture Migration + +## Architecture Migration: Polygon CDK → OP Stack (CRITICAL) + +X Layer migrated from **Polygon CDK (zkEVM)** to **OP Stack (optimistic rollup)** on October 27, 2025. This fundamentally changed the chain's operating principles. + +| | Pre-Re-genesis (CDK) | Post-Re-genesis (OP Stack) | +|---|---|---| +| **Architecture** | Polygon CDK / zkEVM | Optimism OP Stack | +| **Execution client** | cdk-Erigon | Geth / **Reth** (xlayer-reth) | +| **Proof system** | ZK validity proof | Optimistic + dispute games | +| **EVM compatibility** | zkEVM (limited precompile/opcode) | Standard EVM (full compatibility) | +| **Block time** | Variable | ~1 second (fixed) | +| **Finality** | Minutes (ZK proof) | ~7 days (challenge period, PermissionedDisputeGame) | + +**Important:** Post-re-genesis X Layer is fully standard EVM compatible. zkEVM limitations (precompile limits, CREATE2 differences, circuit risks) NO LONGER APPLY — they are only relevant for historical blocks. + +## Re-genesis (CRITICAL) + +Re-genesis occurred at block **42,810,021** (mainnet, October 27, 2025) = **CDK → OP Stack transition point**. +Testnet re-genesis at block **12,241,700**. + +### Routing Rules +| Block Range | Node Type | Architecture | Note | +|---|---|---|---| +| ≤ 42,810,020 | cdk-Erigon | Polygon CDK | `x-qn-height` header may be required (QuickNode) | +| 42,810,021 | — | — | **Untraceable**: `{"code":4444,"message":"pruned history unavailable"}` | +| ≥ 42,810,022 | Geth / Reth | OP Stack | Standard RPC, flashblocks support | + +### Historical Data Queries +Applications requiring archive data MUST implement conditional routing: +```typescript +const RE_GENESIS_BLOCK = 42_810_021; + +async function getBlock(blockNumber: number, provider: ethers.JsonRpcProvider) { + if (blockNumber === RE_GENESIS_BLOCK) { + throw new Error("Block 42810021 is untraceable (re-genesis boundary)"); + } + if (blockNumber < RE_GENESIS_BLOCK) { + // Use cdk-Erigon endpoint (old CDK architecture) + const erigonProvider = new ethers.JsonRpcProvider(ERIGON_RPC_URL); + return erigonProvider.getBlock(blockNumber); + } + // Standard Geth/Reth endpoint (OP Stack) + return provider.getBlock(blockNumber); +} +``` + +**Note:** xlayer-reth node supports this routing **built-in**: +- `--rpc.legacy-url` flag to define legacy cdk-Erigon endpoint +- `--rpc.legacy-timeout` for timeout settings (default: 30s) +- Node automatically routes based on block height + +### Impact +- Block explorer data before 42,810,021 requires a different endpoint +- Event log queries with fromBlock < 42,810,021 need two separate queries +- Account for this boundary in large range `eth_getLogs` queries +- Subgraphs/indexers must handle this boundary + +--- + +## [LEGACY] Pre-Re-genesis zkEVM Limitations + +> **IMPORTANT:** The following limitations apply ONLY to blocks ≤42,810,020 (old Polygon CDK/zkEVM era). Post-re-genesis X Layer runs on **standard EVM compatible** OP Stack — all precompiles and opcodes exhibit standard Ethereum behavior. + +### [LEGACY] Limited Precompiles (pre-re-genesis only) + +| Precompile | Address | CDK Era | OP Stack (Current) | +|---|---|---|---| +| ecRecover | 0x01 | ✅ | ✅ | +| SHA-256 | 0x02 | ✅ | ✅ | +| RIPEMD-160 | 0x03 | ⚠️ Limited | ✅ Full support | +| identity | 0x04 | ✅ | ✅ | +| modexp | 0x05 | ⚠️ Capacity limit | ✅ Full support | +| ecAdd | 0x06 | ✅ | ✅ | +| ecMul | 0x07 | ✅ | ✅ | +| ecPairing | 0x08 | ✅ | ✅ | +| blake2f | 0x09 | ⚠️ Limited | ✅ Full support | +| point evaluation | 0x0a | ⚠️ Limited | ✅ Full support | + +### [LEGACY] CREATE2 Differences (pre-re-genesis only) +- In old zkEVM, `CREATE2` operated through ContractDeployer system contract +- Assembly-level `create2` could produce broken bytecode +- **Post-re-genesis:** Standard Ethereum CREATE2 behavior — full support including assembly + +--- + +## Current (OP Stack) Gas Differences + +| Component | Description | +|---|---| +| L2 execution fee | Standard EVM gas cost (same opcode pricing as Ethereum) | +| L1 data fee | Cost of posting calldata to L1 (variable, depends on L1 gas price) | + +- Always use `eth_estimateGas` for gas estimation (never hardcode) +- L1 data fee directly tied to tx calldata size — calldata compression optimization matters +- Detailed gas information → `gas-optimization.md` + +--- + +## EVM Opcode Behavior Differences (OP Stack — Current) + +### COINBASE +- Ethereum: Block miner/validator address +- X Layer (OP Stack): Sequencer fee vault address (`0x4200000000000000000000000000000000000011`) +- Do NOT use `block.coinbase` for mining reward calculations + +### DIFFICULTY / PREVRANDAO +- Ethereum (post-merge): RANDAO value (pseudo-random) +- X Layer: May return a fixed value or 0 +- Chainlink VRF is NOT available on X Layer — use commit-reveal for on-chain randomness (see `security.md` → Randomness) + +### Block Finality +- Ethereum: ~15 minutes (64 slots) +- X Layer (OP Stack): **~7 day challenge period** (PermissionedDisputeGame active) +- "Soft finality" (sequencer confirmation): ~1 second +- Flashblocks pre-confirmation: ~200ms +- `block.number` and `block.timestamp` are determined by the sequencer +- **Note:** Timing may change when dispute mechanism becomes permissionless + +### SELFDESTRUCT +- Post EIP-6780: Only destroys contracts created in the same transaction +- Avoid usage — deprecated + +### Jovian Hardfork (X Layer Custom) +- Mainnet activation: 2025-12-02 16:00:01 UTC +- Testnet activation: 2025-11-28 11:00:00 UTC +- X Layer-specific protocol improvements (flashblocks, sequencer optimizations) + +--- + +## X Layer-Specific RPC Methods + +### eth_flashblocksEnabled (NEW — OP Stack) +Check if flashblocks are active: +```bash +curl -X POST https://rpc.xlayer.tech \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"eth_flashblocksEnabled","params":[],"id":1}' +``` +- Returns: `true` or `false` +- Check before using flashblocks on nodes that may not support them + +### [LEGACY] zkevm_* RPC Methods (Pre-Re-genesis) + +> These methods only work for querying old CDK era blocks (<42,810,021) — they function on requests routed to legacy cdk-Erigon endpoints. + +#### zkevm_batchNumber +Returns the current batch number: +```bash +curl -X POST \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"zkevm_batchNumber","params":[],"id":1}' +``` + +#### zkevm_batchNumberByBlockNumber +Finds batch number from block number. + +#### zkevm_getBatchByNumber +Returns batch details (tx list, global exit root, timestamp). + +### Use Cases +- **Legacy analytics:** Pre-re-genesis era batch data +- **Historical indexer:** CDK era data indexing +- **Note:** Post-re-genesis blocks use OP Stack sequencer batches — different structure