diff --git a/.env.sample.migration b/.env.sample.migration new file mode 100644 index 00000000..ce900df8 --- /dev/null +++ b/.env.sample.migration @@ -0,0 +1,74 @@ +# ============================================================================= +# SHARED WITH SHASTA (existing env variables) +# ============================================================================= + +CATALYST_NODE_ECDSA_PRIVATE_KEY= +# PRECONFER_ADDRESS= +# WEB3SIGNER_L1_URL= +# WEB3SIGNER_L2_URL= + +L1_RPC_URLS= +L1_BEACON_URL= +BLOB_INDEXER_URL= + +TAIKO_GETH_RPC_URL=ws://127.0.0.1:1234 +TAIKO_GETH_AUTH_RPC_URL=http://127.0.0.1:1235 +TAIKO_DRIVER_URL=http://127.0.0.1:1236 +JWT_SECRET_FILE_PATH=/tmp/jwtsecret + +L1_SLOT_DURATION_SEC=12 +L1_SLOTS_PER_EPOCH=32 +PRECONF_HEARTBEAT_MS=2000 + +TAIKO_ANCHOR_ADDRESS=0x1670010000000000000000000000000000010001 +TAIKO_BRIDGE_L2_ADDRESS=0x0000000000000000000000000000000000000000 + +BLOBS_PER_BATCH=3 +MAX_BLOCKS_PER_BATCH=1 +MAX_TIME_SHIFT_BETWEEN_BLOCKS_SEC=255 +MAX_ANCHOR_HEIGHT_OFFSET_REDUCTION_VALUE=10 + +MAX_BYTES_PER_TX_LIST=126976 +MIN_BYTES_PER_TX_LIST=8192 +THROTTLING_FACTOR=2 +PRECONF_MIN_TXS=3 +PRECONF_MAX_SKIPPED_L2_SLOTS=2 + +MIN_PRIORITY_FEE_PER_GAS_WEI=1000000000 +TX_FEES_INCREASE_PERCENTAGE=0 +MAX_ATTEMPTS_TO_SEND_TX=4 +MAX_ATTEMPTS_TO_WAIT_TX=5 +DELAY_BETWEEN_TX_ATTEMPTS_SEC=63 +EXTRA_GAS_PERCENTAGE=100 + +RPC_L2_EXECUTION_LAYER_TIMEOUT_MS=1000 +RPC_DRIVER_PRECONF_TIMEOUT_MS=60000 +RPC_DRIVER_STATUS_TIMEOUT_MS=1000 + +FUNDS_MONITOR_INTERVAL_SEC=60 +THRESHOLD_ETH=500000000000000000 +THRESHOLD_TAIKO=0 + +DISABLE_BRIDGING=true +AMOUNT_TO_BRIDGE_FROM_L2_TO_L1=1000000000000000000 +BRIDGE_RELAYER_FEE=3047459064000000 +BRIDGE_TRANSACTION_FEE=1000000000000000 + +FORK_SWITCH_TRANSITION_PERIOD_SEC=60 +PACAYA_TIMESTAMP_SEC=0 +SHASTA_TIMESTAMP_SEC=99999999999 +WHITELIST_MONITOR_INTERVAL_SEC=60 + +# ============================================================================= +# NEW FOR REALTIME FORK +# ============================================================================= + +REALTIME_INBOX_ADDRESS= +PROPOSER_MULTICALL_ADDRESS= +L1_BRIDGE_ADDRESS= + +RAIKO_URL=http://localhost:8080 +RAIKO_API_KEY= +RAIKO_PROOF_TYPE=sgx +RAIKO_L2_NETWORK=taiko_mainnet +RAIKO_L1_NETWORK=ethereum diff --git a/.github/workflows/node_docker_build.yml b/.github/workflows/node_docker_build.yml index e31af9a5..4211b7b0 100644 --- a/.github/workflows/node_docker_build.yml +++ b/.github/workflows/node_docker_build.yml @@ -19,7 +19,8 @@ env: DOCKER_PUBLIC_REGISTRY: docker.io DOCKER_PUBLIC_REPOSITORY: nethermind/catalyst-node DOCKER_REGISTRY: nethermind.jfrog.io - DOCKER_REPOSITORY_STAGING: core-oci-local-staging/catalyst-node + DOCKER_REPOSITORY_PROD: core-oci-local-prod/catalyst-node + MASTER_BRANCH: refs/heads/master jobs: build: @@ -50,7 +51,8 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - uses: docker/login-action@v3 + - name: Login to JFrog Artifactory + uses: docker/login-action@v3 with: registry: ${{ env.DOCKER_REGISTRY }} username: ${{ secrets.ARTIFACTORY_CORE_USERNAME }} @@ -64,24 +66,20 @@ jobs: file: Dockerfile platforms: ${{ matrix.platform }} push: true - outputs: type=image,name=${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_REPOSITORY_STAGING }},push-by-digest=true,name-canonical=true + outputs: type=image,name=${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_REPOSITORY_PROD }},push-by-digest=true,name-canonical=true - name: Set digest output id: digest run: | - if [ "${{ matrix.short }}" = "amd64" ]; then - echo "amd64=${{ steps.build.outputs.digest }}" >> $GITHUB_OUTPUT - fi - if [ "${{ matrix.short }}" = "arm64" ]; then - echo "arm64=${{ steps.build.outputs.digest }}" >> $GITHUB_OUTPUT - fi + echo "${{ matrix.short }}=${{ steps.build.outputs.digest }}" >> $GITHUB_OUTPUT merge: name: Merge and push multi-arch manifest runs-on: ubuntu-latest needs: build steps: - - uses: docker/login-action@v3 + - name: Login to JFrog Artifactory + uses: docker/login-action@v3 with: registry: ${{ env.DOCKER_REGISTRY }} username: ${{ secrets.ARTIFACTORY_CORE_USERNAME }} @@ -90,79 +88,139 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Determine event type and set tags - id: event + - name: Calculate SHA tag + id: sha run: | - if [[ "${{ github.ref }}" == refs/tags/* ]]; then - echo "is_tag=true" >> $GITHUB_OUTPUT - echo "is_branch=false" >> $GITHUB_OUTPUT - VERSION=${GITHUB_REF#refs/tags/} - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "tag_list=type=raw,value=$VERSION" >> $GITHUB_OUTPUT - else - echo "is_tag=false" >> $GITHUB_OUTPUT - echo "is_branch=true" >> $GITHUB_OUTPUT + SHORT_SHA="${{ github.sha }}" + SHORT_SHA=${SHORT_SHA:0:7} + echo "tag=sha-${SHORT_SHA}" >> $GITHUB_OUTPUT + echo "short=${SHORT_SHA}" >> $GITHUB_OUTPUT + + - name: Determine tags and event type + id: tags + run: | + REF="${{ github.ref }}" + REALTIME_TAG="realtime-${{ steps.sha.outputs.short }}" + SOURCE_PROMOTE_TAG="${{ steps.sha.outputs.tag }}" + IS_MASTER=false + IS_TAG=false + VERSION_TAG="" + PROMOTE_TAGS="$REALTIME_TAG" + + if [[ "$REF" == refs/tags/* ]]; then + IS_TAG=true + VERSION_TAG=${REF#refs/tags/} + echo "tag_list=type=raw,value=$VERSION_TAG" >> $GITHUB_OUTPUT + elif [[ "$REF" == ${{ env.MASTER_BRANCH }} ]]; then + IS_MASTER=true echo "tag_list=type=raw,value=latest" >> $GITHUB_OUTPUT + else + echo "tag_list=type=sha,value=${{ github.sha }}" >> $GITHUB_OUTPUT fi - - name: Docker meta + echo "is_master=$IS_MASTER" >> $GITHUB_OUTPUT + echo "is_tag=$IS_TAG" >> $GITHUB_OUTPUT + echo "version_tag=$VERSION_TAG" >> $GITHUB_OUTPUT + echo "source_promote_tag=$SOURCE_PROMOTE_TAG" >> $GITHUB_OUTPUT + echo "promote_tags=$PROMOTE_TAGS" >> $GITHUB_OUTPUT + echo "PROMOTE_TAGS=$PROMOTE_TAGS" >> $GITHUB_ENV + + - name: Generate Docker metadata id: meta uses: docker/metadata-action@v5 with: - images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_REPOSITORY_STAGING }} - tags: ${{ steps.event.outputs.tag_list }} + images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_REPOSITORY_PROD }} + tags: ${{ steps.tags.outputs.tag_list }} - - name: Create manifest list and push + - name: Create and push manifest list + run: | + # Filter out "latest" tag if not on master branch + if [[ "${{ steps.tags.outputs.is_master }}" != "true" ]]; then + FILTERED_TAGS=$(jq -cr '.tags | map(select(. | endswith(":latest") | not)) | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") + else + FILTERED_TAGS=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") + fi + + if [ -n "$FILTERED_TAGS" ]; then + docker buildx imagetools create \ + $FILTERED_TAGS \ + ${{ needs.build.outputs.digest-amd64 }} \ + ${{ needs.build.outputs.digest-arm64 }} + fi + + - name: Tag with commit SHA run: | docker buildx imagetools create \ - $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + -t ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_REPOSITORY_PROD }}:${{ steps.sha.outputs.tag }} \ ${{ needs.build.outputs.digest-amd64 }} \ ${{ needs.build.outputs.digest-arm64 }} - name: Setup ORAS uses: oras-project/setup-oras@v1 - - name: Check ORAS version - run: oras version - - - name: Determine tags to promote - id: promote-tags + - name: Login to JFrog Artifactory with ORAS run: | - if [[ "${{ github.ref }}" == refs/tags/* ]]; then - VERSION=${GITHUB_REF#refs/tags/} - BASE_VERSION=${VERSION%} - echo "TAGS=latest $BASE_VERSION" >> $GITHUB_ENV - else - echo "TAGS=latest" >> $GITHUB_ENV - fi + oras login ${{ env.DOCKER_REGISTRY }} \ + -u ${{ secrets.ARTIFACTORY_CORE_USERNAME }} \ + -p ${{ secrets.ARTIFACTORY_CORE_TOKEN_DEVELOPER }} - - name: Login to Dockerhub registry with ORAS + - name: Login to Docker Hub run: | oras login ${{ env.DOCKER_PUBLIC_REGISTRY }} \ -u ${{ secrets.DOCKER_USERNAME }} \ -p ${{ secrets.DOCKER_PASSWORD }} - - name: Promote to Dockerhub Production + - name: Promote to Docker Hub Production run: | - for tag in $TAGS; do - echo "Current tag: $tag" - source_image="${DOCKER_REGISTRY}/${DOCKER_REPOSITORY_STAGING}:${tag}" - prod_image="${DOCKER_PUBLIC_REGISTRY}/${DOCKER_PUBLIC_REPOSITORY}:${tag}" - echo "Promoting ${source_image} to ${prod_image}" - oras cp -r "${source_image}" "${prod_image}" + set -e + SOURCE_TAG="${{ steps.tags.outputs.source_promote_tag }}" + echo "Tags to promote: $PROMOTE_TAGS" + for tag in $PROMOTE_TAGS; do + source_image="${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_REPOSITORY_PROD }}:${SOURCE_TAG}" + prod_image="${{ env.DOCKER_PUBLIC_REGISTRY }}/${{ env.DOCKER_PUBLIC_REPOSITORY }}:${tag}" + echo "" + echo "=== Promoting tag: ${tag} ===" + echo "Source: ${source_image}" + echo "Target: ${prod_image}" + + if ! oras cp -r "${source_image}" "${prod_image}"; then + echo "ERROR: Failed to promote tag ${tag}" >&2 + echo "Source: ${source_image}" >&2 + echo "Target: ${prod_image}" >&2 + exit 1 + fi + echo "✓ Successfully promoted ${tag}" done + echo "" + echo "All tags promoted successfully" - name: Summary run: | echo "## Catalyst Node Docker build Completed" >> $GITHUB_STEP_SUMMARY - echo "### Tags" >> $GITHUB_STEP_SUMMARY - for tag in $TAGS; do - echo "- $tag" >> $GITHUB_STEP_SUMMARY - done - echo "### Notes" >> $GITHUB_STEP_SUMMARY - echo "- The images have been pushed to ${DOCKER_REPOSITORY_STAGING} repo" >> $GITHUB_STEP_SUMMARY - echo "- **STAGING Repository**: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_REPOSITORY_STAGING }}" >> $GITHUB_STEP_SUMMARY - echo "- **PROD Repository**: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_PUBLIC_REGISTRY }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Tags Promoted" >> $GITHUB_STEP_SUMMARY + + # Get tags from step output (more reliable than env var) + TAGS_STRING="${{ steps.tags.outputs.promote_tags }}" + + # If step output is empty, try environment variable + if [ -z "$TAGS_STRING" ]; then + TAGS_STRING="$PROMOTE_TAGS" + fi + + # Split tags by space and display each one + if [ -n "$TAGS_STRING" ]; then + # Convert space-separated string to array and iterate + for tag in $TAGS_STRING; do + echo "- \`${tag}\`" >> $GITHUB_STEP_SUMMARY + done + else + echo "- No tags available" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Repository Information" >> $GITHUB_STEP_SUMMARY + echo "- **Production**: \`${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_REPOSITORY_PROD }}\`" >> $GITHUB_STEP_SUMMARY + echo "- **Production**: \`${{ env.DOCKER_PUBLIC_REGISTRY }}/${{ env.DOCKER_PUBLIC_REPOSITORY }}\`" >> $GITHUB_STEP_SUMMARY echo "- **Platforms**: linux/amd64, linux/arm64" >> $GITHUB_STEP_SUMMARY - echo "- **Commit**: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY - + echo "- **Commit**: \`${{ github.sha }}\` (\`${{ steps.sha.outputs.short }}\`)" >> $GITHUB_STEP_SUMMARY diff --git a/.gitignore b/.gitignore index 972ea9cd..29956ea6 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ venv .DS_Store .sum .devcontainer -.idea \ No newline at end of file +.idea +data/ \ No newline at end of file diff --git a/CATALYST_MIGRATION.md b/CATALYST_MIGRATION.md new file mode 100644 index 00000000..4551daac --- /dev/null +++ b/CATALYST_MIGRATION.md @@ -0,0 +1,1146 @@ +# Catalyst Real-Time Fork Migration Plan + +> Step-by-step plan to migrate Catalyst from the asynchronous Shasta proving model to the +> single-phase **RealTimeInbox** (atomic propose+prove) model. + +--- + +## Table of Contents + +1. [Executive Summary](#1-executive-summary) +2. [Current Architecture (Shasta)](#2-current-architecture-shasta) +3. [Target Architecture (RealTime)](#3-target-architecture-realtime) +4. [Migration Strategy](#4-migration-strategy) +5. [Step 1 — Scaffold the `realtime` Crate](#step-1--scaffold-the-realtime-crate) +6. [Step 2 — Contract Bindings & ABIs](#step-2--contract-bindings--abis) +7. [Step 3 — Configuration & Environment](#step-3--configuration--environment) +8. [Step 4 — Protocol Config Adapter](#step-4--protocol-config-adapter) +9. [Step 5 — Proposal Struct Changes](#step-5--proposal-struct-changes) +10. [Step 6 — Raiko Proof Client](#step-6--raiko-proof-client) +11. [Step 7 — Proposal Transaction Builder](#step-7--proposal-transaction-builder) +12. [Step 8 — L1 Execution Layer](#step-8--l1-execution-layer) +13. [Step 9 — L2 Anchor Transaction](#step-9--l2-anchor-transaction) +14. [Step 10 — Node Main Loop](#step-10--node-main-loop) +15. [Step 11 — Batch Manager / Proposal Manager](#step-11--batch-manager--proposal-manager) +16. [Step 12 — Remove Dead Code](#step-12--remove-dead-code) +17. [Step 13 — Integration Testing](#step-13--integration-testing) +18. [Appendix A — File Mapping (Shasta → RealTime)](#appendix-a--file-mapping-shasta--realtime) +19. [Appendix B — Environment Variable Changes](#appendix-b--environment-variable-changes) +20. [Appendix C — Raiko API Quick Reference](#appendix-c--raiko-api-quick-reference) + +--- + +## 1. Executive Summary + +**Shasta** (current) uses a two-phase model: proposals are submitted to L1 and later proven +by an external prover. Catalyst preconfirms blocks, batches them, and submits via +`SurgeInbox.proposeWithProof()` where the "proof" is a signed checkpoint (161-byte signature). + +**RealTime** (target) collapses propose + prove into a single atomic transaction. +Before submitting to L1, the sequencer must: +1. Execute the L2 blocks locally. +2. Request a ZK proof from Raiko covering those blocks. +3. Submit the proposal + ZK proof to `RealTimeInbox.propose()` in one tx. + +### Key Differences + +| Aspect | Shasta | RealTime | +|---|---|---| +| L1 Contract | `Inbox` (SurgeInbox fork) | `RealTimeInbox` | +| Propose function | `proposeWithProof(data, input, proof, signalSlots)` | `propose(data, checkpoint, proof)` | +| Proof type | Signed checkpoint (161 bytes) | ZK proof from Raiko | +| Prove phase | Separate (external prover) | Embedded in propose tx | +| State tracking | Ring buffer, `CoreState`, proposal IDs | Single `lastProposalHash` | +| Bonds / forced inclusions | Yes | No | +| Batch size | Multiple proposals per proof | Exactly 1 proposal per proof | +| L2 Anchor | `anchorV4WithSignalSlots` | `anchorV4WithSignalSlots` (same) | +| Proposal identification | Sequential `id` | Hash-based (`proposalHash`) | + +--- + +## 2. Current Architecture (Shasta) + +### Data Flow + +``` +L2 Txs → preconfirm_block() → BatchBuilder → Proposal → ProposalTxBuilder + ↓ + build_propose_call() + ↓ + sign checkpoint (161-byte proof) + ↓ + Multicall { user_ops, propose, l1_calls } + ↓ + EIP-4844 blob tx → L1 +``` + +### Key Files + +| Component | Path | +|---|---| +| Entry point | `shasta/src/lib.rs` | +| Node loop | `shasta/src/node/mod.rs` | +| Proposal Manager | `shasta/src/node/proposal_manager/mod.rs` | +| Batch Builder | `shasta/src/node/proposal_manager/batch_builder.rs` | +| Proposal struct | `shasta/src/node/proposal_manager/proposal.rs` | +| TX Builder | `shasta/src/l1/proposal_tx_builder.rs` | +| L1 Execution Layer | `shasta/src/l1/execution_layer.rs` | +| L1 Bindings | `shasta/src/l1/bindings.rs` | +| L1 Config | `shasta/src/l1/config.rs` | +| Protocol Config | `shasta/src/l1/protocol_config.rs` | +| L2 Execution Layer | `shasta/src/l2/execution_layer.rs` | +| L2 Anchor Bindings | `shasta/src/l2/bindings.rs` | +| Forced Inclusion | `shasta/src/forced_inclusion/mod.rs` | +| Bridge Handler | `shasta/src/node/proposal_manager/bridge_handler.rs` | +| Utils / Config | `shasta/src/utils/config.rs` | + +### Current Proof Flow (Shasta) + +In `ProposalTxBuilder::build_proof_data()` (proposal_tx_builder.rs:148-162): +```rust +// 1. ABI-encode the checkpoint (blockNumber, blockHash, stateRoot) +let checkpoint_encoded = checkpoint.abi_encode(); // 96 bytes +// 2. Keccak hash and sign with hardcoded anvil key +let checkpoint_digest = keccak256(&checkpoint_encoded); +let signature = self.checkpoint_signer.sign_hash(&checkpoint_digest).await?; +// 3. Concatenate: [96-byte checkpoint || 65-byte signature] = 161 bytes +``` + +This is submitted as the `proof` parameter to `SurgeInbox.proposeWithProof()`. + +--- + +## 3. Target Architecture (RealTime) + +### Data Flow + +``` +L2 Txs → preconfirm_block() → ProposalManager → Proposal + ↓ + finalize proposal (checkpoint known) + ↓ + Request ZK proof from Raiko + (poll until ready) + ↓ + build_propose_call() + ↓ + Multicall { user_ops, propose, l1_calls } + ↓ + EIP-4844 blob tx → L1 +``` + +### RealTimeInbox Contract Interface + +```solidity +function propose( + bytes calldata _data, // abi.encode(ProposeInput) + ICheckpointStore.Checkpoint calldata _checkpoint, + bytes calldata _proof // ZK proof from Raiko +) external; + +function getLastProposalHash() external view returns (bytes32); +function getConfig() external view returns (Config memory); +// Config = { proofVerifier, signalService, basefeeSharingPctg } +``` + +### Proof Verification On-Chain + +``` +commitmentHash = keccak256(abi.encode( + proposalHash, // keccak256(abi.encode(Proposal)) + checkpoint.blockNumber, + checkpoint.blockHash, + checkpoint.stateRoot +)) + +verifyProof(0, commitmentHash, proof) +``` + +--- + +## 4. Migration Strategy + +The `realtime` crate will be a **separate crate** alongside `shasta`, sharing `common` and +workspace dependencies. Code will be copied from shasta and modified — not forked with feature +flags. + +**Rationale**: The protocol changes are deep enough (different contract, different proof model, +removed features) that a clean separation avoids conditional compilation complexity and makes +each crate self-contained. + +### What to Keep From Shasta +- Multicall batching logic (user ops + propose + l1 calls) +- Bridge handler RPC (port 4545) +- Blob encoding (manifest compression via `taiko_protocol::shasta`) +- L2 anchor construction (`anchorV4WithSignalSlots` — unchanged) +- Slot clock, heartbeat, operator management +- Signal slot handling +- Metrics, watchdog, cancellation + +### What to Remove +- Forced inclusion subsystem (`forced_inclusion/` module) +- Bond management (`getBond`, `deposit`, `withdraw`) +- Proposal ID tracking (sequential IDs → hash-based) +- `CoreState` queries (`getCoreState`, `getInboxState`, `nextProposalId`) +- Ring buffer queries (`getProposalHash`) +- Proving window / liveness checks +- `activationTimestamp` warmup (replace with `getLastProposalHash` check) +- Verifier / handover window logic (no batched proving needed) +- `proposerChecker` / whitelist checks (anyone can propose) + +### What to Add +- Raiko HTTP client for proof generation +- Polling loop for proof readiness +- Proposal hash computation (local, for `parentProposalHash` tracking) +- `maxAnchorBlockNumber` / `maxAnchorBlockHash` fields + +--- + +## Step 1 — Scaffold the `realtime` Crate + +### Directory Structure + +``` +realtime/ +├── Cargo.toml +├── src/ +│ ├── lib.rs # create_realtime_node() +│ ├── raiko/ +│ │ └── mod.rs # Raiko HTTP client +│ ├── l1/ +│ │ ├── mod.rs +│ │ ├── bindings.rs # RealTimeInbox + Multicall bindings +│ │ ├── config.rs # Contract addresses +│ │ ├── execution_layer.rs # L1 interaction (slimmed) +│ │ ├── proposal_tx_builder.rs # Build propose tx with ZK proof +│ │ ├── protocol_config.rs # 3-field config from RealTimeInbox +│ │ └── abi/ +│ │ ├── RealTimeInbox.json # From realtime/RealtimeInbox.json +│ │ └── Multicall.json # Copied from shasta +│ ├── l2/ +│ │ ├── mod.rs +│ │ ├── bindings.rs # Anchor bindings (new ABI) +│ │ ├── execution_layer.rs # Mostly unchanged +│ │ ├── extra_data.rs # Copied (or removed if no proposal_id) +│ │ └── abi/ +│ │ └── Anchor.json # From realtime/Anchor.json +│ ├── node/ +│ │ ├── mod.rs # Simplified main loop +│ │ └── proposal_manager/ +│ │ ├── mod.rs # Slimmed ProposalManager +│ │ ├── proposal.rs # New Proposal struct +│ │ ├── batch_builder.rs # Simplified builder +│ │ ├── l2_block_payload.rs # Copied +│ │ └── bridge_handler.rs # Copied +│ ├── shared_abi/ +│ │ ├── mod.rs +│ │ ├── bindings.rs +│ │ └── Bridge.json # Copied from shasta +│ ├── chain_monitor/ +│ │ └── mod.rs # Listen for ProposedAndProved events +│ ├── metrics/ +│ │ └── mod.rs +│ └── utils/ +│ └── config.rs # RealtimeConfig +``` + +### Cargo.toml + +```toml +[package] +name = "realtime" +version = "0.1.0" +edition = "2021" + +[dependencies] +# Same workspace deps as shasta, plus: +reqwest = { version = "0.12", features = ["json"] } # For Raiko HTTP client +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +# Workspace dependencies (same as shasta) +alloy = { workspace = true } +alloy-json-rpc = { workspace = true } +alloy-rlp = { workspace = true } +anyhow = { workspace = true } +common = { workspace = true } +taiko_alethia_reth = { workspace = true } +taiko_bindings = { workspace = true } +taiko_protocol = { workspace = true } +taiko_rpc = { workspace = true } +pacaya = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } +jsonrpsee = { workspace = true } +``` + +### Actions + +1. Create the directory structure above. +2. Copy `Cargo.toml` from shasta, rename package to `realtime`, add `reqwest`. +3. Register `realtime` in the workspace `Cargo.toml`. +4. Copy bridge handler, l2_block_payload, shared_abi verbatim — these are unchanged. + +--- + +## Step 2 — Contract Bindings & ABIs + +### 2.1 Move ABIs + +``` +realtime/RealtimeInbox.json → realtime/src/l1/abi/RealTimeInbox.json +realtime/Anchor.json → realtime/src/l2/abi/Anchor.json +shasta/src/l1/abi/Multicall.json → realtime/src/l1/abi/Multicall.json (copy) +``` + +### 2.2 L1 Bindings (`realtime/src/l1/bindings.rs`) + +```rust +use alloy::sol; + +sol!( + #[allow(missing_docs)] + #[sol(rpc)] + RealTimeInbox, + "src/l1/abi/RealTimeInbox.json" +); + +sol!( + #[allow(missing_docs)] + #[sol(rpc)] + #[derive(Debug)] + Multicall, + "src/l1/abi/Multicall.json" +); +``` + +**Key changes vs shasta bindings:** +- `SurgeInbox` → `RealTimeInbox` +- Generated types will include: + - `RealTimeInbox::proposeCall` (data, checkpoint, proof) + - `IRealTimeInbox::Config` { proofVerifier, signalService, basefeeSharingPctg } + - `IRealTimeInbox::ProposeInput` { blobReference, signalSlots, maxAnchorBlockNumber } + - `ProposedAndProved` event + +### 2.3 L2 Bindings (`realtime/src/l2/bindings.rs`) + +Copy from shasta but point to the new Anchor ABI: + +```rust +sol!( + #[allow(missing_docs)] + #[sol(rpc)] + Anchor, + "src/l2/abi/Anchor.json" +); +``` + +The new Anchor ABI includes `anchorV4WithSignalSlots` (same as shasta) plus the new `anchorV5`. +For the initial migration, continue using `anchorV4WithSignalSlots`. + +### 2.4 Type Mapping + +| Shasta Type | RealTime Type | Notes | +|---|---|---| +| `IInbox::ProposeInput` { deadline, blobReference, numForcedInclusions } | `IRealTimeInbox::ProposeInput` { blobReference, signalSlots, maxAnchorBlockNumber } | No deadline, no forced inclusions; signal slots and max anchor block are first-class | +| `IInbox::Config` (17 fields) | `IRealTimeInbox::Config` (3 fields) | Only proofVerifier, signalService, basefeeSharingPctg | +| `IInbox::CoreState` | N/A (removed) | Replaced by `getLastProposalHash()` | +| `ICheckpointStore::Checkpoint` | `ICheckpointStore::Checkpoint` | Unchanged | + +--- + +## Step 3 — Configuration & Environment + +### 3.1 RealTime Config (`realtime/src/utils/config.rs`) + +```rust +use alloy::primitives::Address; + +pub struct RealtimeConfig { + pub realtime_inbox: Address, // REALTIME_INBOX_ADDRESS + pub proposer_multicall: Address, // PROPOSER_MULTICALL_ADDRESS (same) + pub bridge: Address, // L1_BRIDGE_ADDRESS (same) + pub raiko_url: String, // RAIKO_URL + pub raiko_api_key: Option, // RAIKO_API_KEY (optional) + pub proof_type: String, // RAIKO_PROOF_TYPE (e.g. "sgx", "sp1", "native") + pub raiko_network: String, // RAIKO_L2_NETWORK + pub raiko_l1_network: String, // RAIKO_L1_NETWORK +} + +impl RealtimeConfig { + pub fn read_env_variables() -> Result { + Ok(Self { + realtime_inbox: std::env::var("REALTIME_INBOX_ADDRESS")?.parse()?, + proposer_multicall: std::env::var("PROPOSER_MULTICALL_ADDRESS")?.parse()?, + bridge: std::env::var("L1_BRIDGE_ADDRESS")?.parse()?, + raiko_url: std::env::var("RAIKO_URL") + .unwrap_or_else(|_| "http://localhost:8080".to_string()), + raiko_api_key: std::env::var("RAIKO_API_KEY").ok(), + proof_type: std::env::var("RAIKO_PROOF_TYPE") + .unwrap_or_else(|_| "sgx".to_string()), + raiko_network: std::env::var("RAIKO_L2_NETWORK") + .unwrap_or_else(|_| "taiko_mainnet".to_string()), + raiko_l1_network: std::env::var("RAIKO_L1_NETWORK") + .unwrap_or_else(|_| "ethereum".to_string()), + }) + } +} +``` + +### 3.2 Contract Addresses (`realtime/src/l1/config.rs`) + +```rust +pub struct ContractAddresses { + pub realtime_inbox: Address, // Was: shasta_inbox + pub proposer_multicall: Address, // Same + pub bridge: Address, // Same + // REMOVED: proposer_checker (anyone can propose in RealTime) +} +``` + +--- + +## Step 4 — Protocol Config Adapter + +### `realtime/src/l1/protocol_config.rs` + +```rust +// RealTimeInbox.getConfig() returns only 3 fields +use crate::l1::bindings::IRealTimeInbox::Config; + +#[derive(Clone, Default)] +pub struct ProtocolConfig { + pub basefee_sharing_pctg: u8, + pub proof_verifier: Address, + pub signal_service: Address, +} + +impl From<&Config> for ProtocolConfig { + fn from(config: &Config) -> Self { + Self { + basefee_sharing_pctg: config.basefeeSharingPctg, + proof_verifier: config.proofVerifier, + signal_service: config.signalService, + } + } +} +``` + +**Removed**: `max_anchor_offset` is no longer read from contract config. Use a constant +or derive from the `blockhash()` 256-block limit. + +--- + +## Step 5 — Proposal Struct Changes + +### `realtime/src/node/proposal_manager/proposal.rs` + +```rust +use alloy::primitives::{Address, B256, FixedBytes}; + +#[derive(Default, Clone)] +pub struct Proposal { + // REMOVED: pub id: u64 — no sequential IDs + pub l2_blocks: Vec, + pub total_bytes: u64, + pub coinbase: Address, + + // CHANGED: anchor → maxAnchor + pub max_anchor_block_number: u64, // Was: anchor_block_id + pub max_anchor_block_hash: B256, // Was: anchor_block_hash (now read from blockhash()) + // REMOVED: anchor_block_timestamp_sec — not needed + // REMOVED: anchor_state_root — not in RealTime proposal + + // REMOVED: num_forced_inclusion — no forced inclusions + + // Proof fields + pub checkpoint: Checkpoint, // Same as shasta + pub parent_proposal_hash: B256, // NEW: hash chain tracking + + // Surge POC fields (carried over) + pub user_ops: Vec, + pub signal_slots: Vec>, + pub l1_calls: Vec, + + // NEW: ZK proof (populated after Raiko call) + pub zk_proof: Option>, +} +``` + +### Proposal Hash Computation + +The proposal hash must be computed locally to track `parentProposalHash`: + +```rust +impl Proposal { + /// Compute the proposalHash as the on-chain contract does: + /// keccak256(abi.encode( + /// parentProposalHash, + /// maxAnchorBlockNumber, // padded to 32 bytes + /// maxAnchorBlockHash, + /// basefeeSharingPctg, // padded to 32 bytes + /// sources[], // dynamic array + /// signalSlotsHash + /// )) + pub fn compute_proposal_hash(&self, basefee_sharing_pctg: u8) -> B256 { + use alloy::sol_types::SolValue; + + let signal_slots_hash = if self.signal_slots.is_empty() { + B256::ZERO + } else { + alloy::primitives::keccak256(self.signal_slots.abi_encode()) + }; + + // Build the sources array (DerivationSource[]) + // ... (from blob sidecar data) + + let encoded = ( + self.parent_proposal_hash, + alloy::primitives::U256::from(self.max_anchor_block_number), + self.max_anchor_block_hash, + alloy::primitives::U256::from(basefee_sharing_pctg), + // sources encoding... + signal_slots_hash, + ).abi_encode(); + + alloy::primitives::keccak256(encoded) + } +} +``` + +--- + +## Step 6 — Raiko Proof Client + +This is the **biggest new component**. It does not exist in shasta. + +### `realtime/src/raiko/mod.rs` + +```rust +use anyhow::Error; +use reqwest::Client; +use serde::{Deserialize, Serialize}; +use std::time::Duration; +use tracing::{debug, info, warn}; + +#[derive(Clone)] +pub struct RaikoClient { + client: Client, + base_url: String, + api_key: Option, + proof_type: String, + l2_network: String, + l1_network: String, + prover_address: String, + poll_interval: Duration, + max_retries: u32, +} + +#[derive(Serialize)] +pub struct RaikoProofRequest { + pub l2_block_numbers: Vec, + pub proof_type: String, + pub max_anchor_block_number: u64, + pub parent_proposal_hash: String, // "0x..." + pub basefee_sharing_pctg: u8, + #[serde(skip_serializing_if = "Option::is_none")] + pub network: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub l1_network: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub prover: Option, + pub signal_slots: Vec, // "0x..." hex strings + pub sources: Vec, // DerivationSource[] + pub checkpoint: Option, + pub blob_proof_type: String, +} + +#[derive(Serialize, Deserialize)] +pub struct RaikoCheckpoint { + pub block_number: u64, + pub block_hash: String, + pub state_root: String, +} + +#[derive(Deserialize)] +pub struct RaikoResponse { + pub status: String, // "ok" or "error" + #[serde(default)] + pub proof_type: Option, + #[serde(default)] + pub data: Option, + #[serde(default)] + pub error: Option, + #[serde(default)] + pub message: Option, +} + +#[derive(Deserialize)] +#[serde(untagged)] +pub enum RaikoData { + Proof { proof: String }, + Status { status: String }, +} + +impl RaikoClient { + pub fn new(config: &RealtimeConfig, prover_address: String) -> Self { /* ... */ } + + /// Request a proof and poll until ready. + /// Returns the raw proof bytes. + pub async fn get_proof(&self, request: RaikoProofRequest) -> Result, Error> { + let url = format!("{}/v3/proof/batch/realtime", self.base_url); + + for attempt in 0..self.max_retries { + let mut req = self.client.post(&url) + .json(&request); + + if let Some(ref key) = self.api_key { + req = req.header("X-API-KEY", key); + } + + let resp = req.send().await?; + let body: RaikoResponse = resp.json().await?; + + if body.status == "error" { + return Err(anyhow::anyhow!( + "Raiko proof failed: {}", + body.message.unwrap_or_default() + )); + } + + match body.data { + Some(RaikoData::Proof { proof }) => { + info!("ZK proof received (attempt {})", attempt + 1); + // Decode hex proof to bytes + let proof_bytes = hex::decode(proof.trim_start_matches("0x"))?; + return Ok(proof_bytes); + } + Some(RaikoData::Status { ref status }) if status == "ZKAnyNotDrawn" => { + warn!("Raiko: ZK prover not drawn for this request"); + return Err(anyhow::anyhow!("ZK prover not drawn")); + } + Some(RaikoData::Status { ref status }) => { + debug!("Raiko status: {}, polling... (attempt {})", status, attempt + 1); + tokio::time::sleep(self.poll_interval).await; + } + None => { + return Err(anyhow::anyhow!("Raiko: unexpected empty response")); + } + } + } + + Err(anyhow::anyhow!("Raiko: proof not ready after {} attempts", self.max_retries)) + } +} +``` + +### Integration Point + +The Raiko client is called **after** a batch is finalized (all L2 blocks executed, checkpoint +known) and **before** the L1 transaction is built. This is the critical new step in the pipeline. + +--- + +## Step 7 — Proposal Transaction Builder + +### `realtime/src/l1/proposal_tx_builder.rs` + +Major changes from shasta's `ProposalTxBuilder`: + +1. **Remove `checkpoint_signer`** — no more signed checkpoint proofs. +2. **Remove `build_proof_data()`** — replaced by Raiko ZK proof. +3. **Change `build_propose_call()`** to use `RealTimeInbox.propose()` with 3 params. + +```rust +pub struct ProposalTxBuilder { + provider: DynProvider, + extra_gas_percentage: u64, + raiko_client: RaikoClient, + // REMOVED: checkpoint_signer +} + +impl ProposalTxBuilder { + async fn build_propose_call( + &self, + batch: &Proposal, + inbox_address: Address, + ) -> Result<(Multicall::Call, BlobTransactionSidecar), Error> { + // 1. Build blob sidecar (same as shasta) + let (sidecar, _manifest_data) = self.build_blob_sidecar(batch)?; + + // 2. Build ProposeInput (NEW structure) + // RealTimeInbox ProposeInput = { blobReference, signalSlots, maxAnchorBlockNumber } + let input = IRealTimeInbox::ProposeInput { + blobReference: BlobReference { + blobStartIndex: 0, + numBlobs: sidecar.blobs.len().try_into()?, + offset: U24::ZERO, + }, + signalSlots: batch.signal_slots.clone(), + maxAnchorBlockNumber: U48::from(batch.max_anchor_block_number), + }; + + // 3. Encode the input + let inbox = RealTimeInbox::new(inbox_address, self.provider.clone()); + let encoded_input = inbox.encodeProposeInput(input).call().await?; + + // 4. Use the ZK proof from Raiko (already obtained) + let proof = Bytes::from( + batch.zk_proof.as_ref() + .ok_or_else(|| anyhow::anyhow!("ZK proof not set on proposal"))? + .clone() + ); + + // 5. Build the propose call with 3 parameters: + // propose(bytes _data, Checkpoint _checkpoint, bytes _proof) + let call = inbox.propose( + encoded_input, // _data = abi.encode(ProposeInput) + batch.checkpoint.clone(), + proof, + ); + + Ok(( + Multicall::Call { + target: inbox_address, + value: U256::ZERO, + data: call.calldata().clone(), + }, + sidecar, + )) + } +} +``` + +### Multicall Composition (unchanged pattern) + +The multicall still follows the same pattern: +1. User ops (optional) +2. Propose call (with ZK proof instead of signed checkpoint) +3. L1 calls (optional) + +--- + +## Step 8 — L1 Execution Layer + +### `realtime/src/l1/execution_layer.rs` + +Key changes from shasta: + +```rust +pub struct ExecutionLayer { + common: ExecutionLayerCommon, + provider: DynProvider, + preconfer_address: Address, + pub transaction_monitor: TransactionMonitor, + contract_addresses: ContractAddresses, + // CHANGED: InboxInstance → RealTimeInboxInstance + realtime_inbox: RealTimeInbox::RealTimeInboxInstance, + // REMOVED: checkpoint_signer (no more signed proofs) +} +``` + +### Removed Methods + +- `get_inbox_state()` → removed (no CoreState) +- `get_inbox_next_proposal_id()` → removed (no sequential IDs) +- `get_activation_timestamp()` → removed (RealTimeInbox uses `activate()` differently) +- `get_forced_inclusion_*()` → removed (no forced inclusions) +- `get_preconfer_total_bonds()` → removed (no bonds) +- `is_operator_whitelisted()` → removed (anyone can propose) + +### New Methods + +```rust +impl ExecutionLayer { + /// Get the last proposal hash from RealTimeInbox + pub async fn get_last_proposal_hash(&self) -> Result { + let hash = self.realtime_inbox + .getLastProposalHash() + .call() + .await + .map_err(|e| anyhow::anyhow!("Failed to call getLastProposalHash: {e}"))?; + Ok(hash) + } + + /// Fetch the 3-field config from RealTimeInbox + pub async fn fetch_protocol_config(&self) -> Result { + let config = self.realtime_inbox + .getConfig() + .call() + .await + .map_err(|e| anyhow::anyhow!("Failed to call getConfig: {e}"))?; + Ok(ProtocolConfig::from(&config.config_)) + } +} +``` + +### Warmup Changes + +Replace the `activationTimestamp` wait loop with `getLastProposalHash` check: + +```rust +async fn warmup(&mut self) -> Result<(), Error> { + // Wait for RealTimeInbox activation (lastProposalHash != 0) + loop { + let hash = self.ethereum_l1.execution_layer + .get_last_proposal_hash().await?; + if hash != B256::ZERO { + info!("RealTimeInbox is active, lastProposalHash: {}", hash); + break; + } + warn!("RealTimeInbox not yet activated. Waiting..."); + sleep(Duration::from_secs(12)).await; + } + Ok(()) +} +``` + +--- + +## Step 9 — L2 Anchor Transaction + +### No Changes Required + +The L2 execution layer already uses `anchorV4WithSignalSlots`, which is the correct anchor +function for the RealTime fork. The Anchor contract ABI from `realtime/Anchor.json` includes +this function. + +The only change is the ABI file path in the bindings — point to the new `Anchor.json`. + +### Anchor Call (unchanged logic) + +```rust +// realtime/src/l2/execution_layer.rs +// Same as shasta/src/l2/execution_layer.rs:105-107 +let call_builder = self + .shasta_anchor + .anchorV4WithSignalSlots(anchor_block_params.0, anchor_block_params.1); +``` + +### Note on `anchorV5` + +The new Anchor ABI includes `anchorV5` with `ProposalParams` and `BlockParams`. This is for +future use. The initial migration should continue using `anchorV4WithSignalSlots`. + +--- + +## Step 10 — Node Main Loop + +### `realtime/src/node/mod.rs` + +The main loop is **simplified** because: +- No verifier (no separate proving window to monitor) +- No forced inclusion handling +- No proposer checker / whitelist validation +- No bond management +- Proof is obtained before submission (synchronous from node's perspective) + +### Simplified Loop + +```rust +async fn main_block_preconfirmation_step(&mut self) -> Result<(), Error> { + let (l2_slot_info, current_status, pending_tx_list) = + self.get_slot_info_and_status().await?; + + let transaction_in_progress = self.ethereum_l1.execution_layer + .is_transaction_in_progress().await?; + + // Preconfirmation phase + if current_status.is_preconfer() && current_status.is_driver_synced() { + // Head verification (same as shasta) + if !self.head_verifier.verify(...).await { /* ... */ } + + let l2_slot_context = L2SlotContext { /* ... */ }; + + if self.proposal_manager.should_new_block_be_created(&pending_tx_list, &l2_slot_context) { + if has_pending_txs || has_pending_user_ops { + let preconfed_block = self.proposal_manager + .preconfirm_block(pending_tx_list, &l2_slot_context).await?; + self.verify_preconfed_block(preconfed_block).await?; + } + } + } + + // Submission phase — NOW includes proof fetching + if current_status.is_submitter() && !transaction_in_progress { + // No verifier check needed — just submit if we have finalized batches + if self.proposal_manager.has_batches_ready_to_submit() { + self.proposal_manager.try_submit_oldest_batch().await?; + } + } + + // Cleanup (simplified — no verifier to clear) + if !current_status.is_submitter() && !current_status.is_preconfer() { + if self.proposal_manager.has_batches() { + self.proposal_manager.reset_builder().await?; + } + } + + Ok(()) +} +``` + +### Removed from Loop +- `check_for_missing_proposed_batches()` — no proposal IDs to compare +- `has_verified_unproposed_batches()` — no external verifier +- `check_and_handle_anchor_offset_for_unsafe_l2_blocks()` — simplified (use 256-block limit) +- `get_next_proposal_id()` — no sequential IDs +- Forced inclusion checks + +--- + +## Step 11 — Batch Manager / Proposal Manager + +### Key Change: Proof Fetching Before Submission + +The batch submission flow now has an additional step between finalization and L1 submission: + +``` +finalize_current_batch() + ↓ +fetch_proof_from_raiko() ← NEW + ↓ +send_batch_to_l1() +``` + +### `try_submit_oldest_batch()` (modified) + +```rust +pub async fn try_submit_oldest_batch(&mut self) -> Result<(), Error> { + if let Some(batch) = self.proposals_to_send.front_mut() { + // Step 1: If proof not yet obtained, fetch from Raiko + if batch.zk_proof.is_none() { + let l2_block_numbers: Vec = batch.l2_blocks.iter() + .map(|b| /* get block number from checkpoint or sequential */) + .collect(); + + let request = RaikoProofRequest { + l2_block_numbers, + proof_type: self.raiko_client.proof_type.clone(), + max_anchor_block_number: batch.max_anchor_block_number, + parent_proposal_hash: format!("0x{}", hex::encode(batch.parent_proposal_hash)), + basefee_sharing_pctg: self.protocol_config.basefee_sharing_pctg, + signal_slots: batch.signal_slots.iter() + .map(|s| format!("0x{}", hex::encode(s))) + .collect(), + sources: vec![], // Build from blob data + checkpoint: Some(RaikoCheckpoint { + block_number: batch.checkpoint.blockNumber.to::(), + block_hash: format!("0x{}", hex::encode(batch.checkpoint.blockHash)), + state_root: format!("0x{}", hex::encode(batch.checkpoint.stateRoot)), + }), + // ... other fields + }; + + let proof = self.raiko_client.get_proof(request).await?; + batch.zk_proof = Some(proof); + } + + // Step 2: Submit to L1 (same as shasta, but with ZK proof) + self.ethereum_l1.execution_layer + .send_batch_to_l1(batch.clone(), None, None) + .await?; + + self.proposals_to_send.pop_front(); + } + Ok(()) +} +``` + +### Proposal Hash Tracking + +Since RealTimeInbox uses `lastProposalHash` instead of sequential IDs, the manager must: + +1. **On startup**: Read `getLastProposalHash()` from L1 to initialize `parent_proposal_hash`. +2. **After each submission**: Compute and store the new proposal hash locally. +3. **Use `parent_proposal_hash`** when creating each new proposal. + +```rust +pub struct ProposalManager { + // ... + parent_proposal_hash: B256, // Tracks the chain head +} + +impl ProposalManager { + async fn create_new_batch(&mut self) -> Result<(), Error> { + // Read current L1 block for maxAnchorBlockNumber + let l1_block = self.ethereum_l1.execution_layer.common() + .get_latest_block_number().await?; + + // Ensure it's within 256 blocks (blockhash() limit) + let max_anchor = l1_block.saturating_sub(self.l1_height_lag); + + let max_anchor_hash = self.ethereum_l1.execution_layer.common() + .get_block_hash_by_number(max_anchor).await?; + + self.batch_builder.create_new_batch( + max_anchor, + max_anchor_hash, + self.parent_proposal_hash, + ); + + Ok(()) + } +} +``` + +--- + +## Step 12 — Remove Dead Code + +Files/modules from shasta that should NOT be copied to realtime: + +| Module | Reason | +|---|---| +| `forced_inclusion/mod.rs` | No forced inclusions in RealTime | +| `node/verifier.rs` | No separate proving window / handover verification | +| `node/l2_height_from_l1.rs` | Based on proposal ID lookups (replaced by hash tracking) | +| `l2/extra_data.rs` | Encodes `proposal_id` into block extra data (no IDs in RealTime) | + +Dependencies to remove from trait implementations: + +| Trait | Reason | +|---|---| +| `PreconferBondProvider` | No bonds | +| `WhitelistProvider` | No whitelist | + +--- + +## Step 13 — Integration Testing + +### 13.1 Unit Tests + +1. **Proposal hash computation** — verify local hash matches contract's `hashProposal()`. +2. **Commitment hash computation** — verify local commitment hash matches contract's `hashCommitment()`. +3. **Signal slots hash** — verify `bytes32(0)` for empty, `keccak256(abi.encode(slots))` for non-empty. +4. **ProposeInput encoding** — verify `encodeProposeInput()` output matches expectations. + +### 13.2 Integration Tests + +1. **Raiko client** — mock server returning Registered → WorkInProgress → proof. +2. **Full pipeline** — local anvil + mock Raiko: + - Preconfirm block → finalize → fetch proof → submit to RealTimeInbox. +3. **Multicall composition** — user op + propose + l1 call in one tx. +4. **Chain recovery** — restart node, read `getLastProposalHash()`, resume. + +### 13.3 E2E Test Script + +```bash +# 1. Deploy RealTimeInbox on local anvil +# 2. Activate with genesis hash +# 3. Start Raiko mock (return native proof) +# 4. Start realtime node +# 5. Send L2 transactions +# 6. Verify ProposedAndProved event emitted +# 7. Verify lastProposalHash updated +``` + +--- + +## Appendix A — File Mapping (Shasta → RealTime) + +| Shasta File | RealTime File | Action | +|---|---|---| +| `lib.rs` | `lib.rs` | Rewrite (simplified init) | +| `node/mod.rs` | `node/mod.rs` | Rewrite (simplified loop) | +| `node/verifier.rs` | — | Delete | +| `node/l2_height_from_l1.rs` | — | Delete | +| `node/proposal_manager/mod.rs` | `node/proposal_manager/mod.rs` | Heavy edit (add Raiko, remove FI) | +| `node/proposal_manager/proposal.rs` | `node/proposal_manager/proposal.rs` | Rewrite (new fields) | +| `node/proposal_manager/batch_builder.rs` | `node/proposal_manager/batch_builder.rs` | Edit (remove FI, ID tracking) | +| `node/proposal_manager/bridge_handler.rs` | `node/proposal_manager/bridge_handler.rs` | Copy verbatim | +| `node/proposal_manager/l2_block_payload.rs` | `node/proposal_manager/l2_block_payload.rs` | Copy (remove proposal_id if needed) | +| `l1/bindings.rs` | `l1/bindings.rs` | Rewrite (RealTimeInbox) | +| `l1/config.rs` | `l1/config.rs` | Edit (remove proposer_checker) | +| `l1/execution_layer.rs` | `l1/execution_layer.rs` | Heavy edit (new methods, remove old) | +| `l1/proposal_tx_builder.rs` | `l1/proposal_tx_builder.rs` | Rewrite (ZK proof, new propose call) | +| `l1/protocol_config.rs` | `l1/protocol_config.rs` | Rewrite (3-field config) | +| `l1/abi/SurgeInbox.json` | `l1/abi/RealTimeInbox.json` | Replace | +| `l1/abi/Multicall.json` | `l1/abi/Multicall.json` | Copy | +| `l2/execution_layer.rs` | `l2/execution_layer.rs` | Copy (minor path changes) | +| `l2/bindings.rs` | `l2/bindings.rs` | Copy (new Anchor ABI path) | +| `l2/extra_data.rs` | — | Delete (or adapt if extra_data still needed) | +| `l2/abi/Anchor.json` | `l2/abi/Anchor.json` | Replace with new ABI | +| `forced_inclusion/mod.rs` | — | Delete | +| `chain_monitor/mod.rs` | `chain_monitor/mod.rs` | Edit (listen for ProposedAndProved) | +| `shared_abi/*` | `shared_abi/*` | Copy verbatim | +| `utils/config.rs` | `utils/config.rs` | Rewrite (new env vars) | +| — | `raiko/mod.rs` | **New** | + +--- + +## Appendix B — Environment Variable Changes + +| Variable | Shasta | RealTime | Notes | +|---|---|---|---| +| `SHASTA_INBOX_ADDRESS` | Required | — | Removed | +| `REALTIME_INBOX_ADDRESS` | — | Required | **New** | +| `PROPOSER_MULTICALL_ADDRESS` | Required | Required | Same | +| `L1_BRIDGE_ADDRESS` | Required | Required | Same | +| `RAIKO_URL` | — | Required | **New** — e.g. `http://localhost:8080` | +| `RAIKO_API_KEY` | — | Optional | **New** — for authenticated Raiko | +| `RAIKO_PROOF_TYPE` | — | Optional | **New** — default `sgx` | +| `RAIKO_L2_NETWORK` | — | Optional | **New** — default `taiko_mainnet` | +| `RAIKO_L1_NETWORK` | — | Optional | **New** — default `ethereum` | + +--- + +## Appendix C — Raiko API Quick Reference + +### Endpoint + +``` +POST {RAIKO_URL}/v3/proof/batch/realtime +``` + +### Request (minimum required fields) + +```json +{ + "l2_block_numbers": [100, 101, 102], + "proof_type": "sgx", + "max_anchor_block_number": 19500000, + "parent_proposal_hash": "0x00...00", + "basefee_sharing_pctg": 0 +} +``` + +### Response States + +| Response | Meaning | Action | +|---|---|---| +| `data.proof` present | Proof ready | Use it | +| `data.status: "Registered"` | Queued | Poll (same request) | +| `data.status: "WorkInProgress"` | Generating | Poll (same request) | +| `data.status: "ZKAnyNotDrawn"` | Not selected | Don't retry | +| `status: "error"` | Failed | Check `message` | + +### Polling Model + +Re-submit the **identical** request body. The server deduplicates by request key. +Recommended interval: 5-30 seconds. + +See [FETCH_REAL_TIME_PROOF.md](FETCH_REAL_TIME_PROOF.md) for the full API specification. + +--- + +## Summary Checklist + +- [ ] Scaffold `realtime/` crate with Cargo.toml +- [ ] Copy and place ABIs (RealTimeInbox, Anchor, Multicall, Bridge) +- [ ] Create L1/L2 bindings with new ABIs +- [ ] Implement `RealtimeConfig` with new env vars +- [ ] Implement `ProtocolConfig` (3-field) +- [ ] Rewrite `Proposal` struct (hash-based, max anchor, no ID) +- [ ] Implement `RaikoClient` with polling +- [ ] Rewrite `ProposalTxBuilder` (ZK proof, new propose signature) +- [ ] Rewrite `ExecutionLayer` (getLastProposalHash, remove bonds/FI) +- [ ] Copy L2 execution layer (anchor unchanged) +- [ ] Simplify node main loop (remove verifier, FI handling) +- [ ] Modify `ProposalManager` (add Raiko call before submission) +- [ ] Implement proposal hash tracking (parent chain) +- [ ] Remove dead code (forced inclusion, verifier, bonds) +- [ ] Update chain monitor for `ProposedAndProved` events +- [ ] Write unit tests for hash computation +- [ ] Write integration tests with mock Raiko +- [ ] E2E test with local anvil diff --git a/Cargo.lock b/Cargo.lock index 1cff83e5..84917dfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -623,7 +623,7 @@ dependencies = [ "futures", "futures-utils-wasm", "lru 0.16.3", - "parking_lot", + "parking_lot 0.12.5", "pin-project", "reqwest 0.12.28", "serde", @@ -647,7 +647,7 @@ dependencies = [ "auto_impl", "bimap", "futures", - "parking_lot", + "parking_lot 0.12.5", "serde", "serde_json", "tokio", @@ -956,7 +956,7 @@ dependencies = [ "derive_more", "futures", "futures-utils-wasm", - "parking_lot", + "parking_lot 0.12.5", "serde", "serde_json", "thiserror 2.0.18", @@ -2515,7 +2515,7 @@ dependencies = [ "hashbrown 0.14.5", "lock_api", "once_cell", - "parking_lot_core", + "parking_lot_core 0.9.12", "serde", ] @@ -2764,7 +2764,7 @@ dependencies = [ "lru 0.12.5", "more-asserts", "multiaddr", - "parking_lot", + "parking_lot 0.12.5", "rand 0.8.5", "smallvec", "socket2 0.5.10", @@ -2797,7 +2797,7 @@ dependencies = [ "lru 0.12.5", "more-asserts", "multiaddr", - "parking_lot", + "parking_lot 0.12.5", "rand 0.8.5", "smallvec", "socket2 0.5.10", @@ -3033,7 +3033,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -3279,6 +3279,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "fs_extra" version = "1.3.0" @@ -3360,7 +3370,7 @@ checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" dependencies = [ "futures-core", "lock_api", - "parking_lot", + "parking_lot 0.12.5", ] [[package]] @@ -3442,6 +3452,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -3723,7 +3742,7 @@ dependencies = [ "ipconfig", "moka", "once_cell", - "parking_lot", + "parking_lot 0.12.5", "rand 0.9.2", "resolv-conf", "serde", @@ -3909,7 +3928,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.2", "system-configuration 0.7.0", "tokio", "tower-layer", @@ -4225,6 +4244,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + [[package]] name = "ipconfig" version = "0.3.2" @@ -4363,7 +4391,7 @@ dependencies = [ "http-body", "http-body-util", "jsonrpsee-types", - "parking_lot", + "parking_lot 0.12.5", "pin-project", "rand 0.9.2", "rustc-hash", @@ -4785,7 +4813,7 @@ dependencies = [ "multiaddr", "multihash", "multistream-select", - "parking_lot", + "parking_lot 0.12.5", "pin-project", "quick-protobuf", "rand 0.8.5", @@ -4807,7 +4835,7 @@ dependencies = [ "hickory-resolver", "libp2p-core", "libp2p-identity", - "parking_lot", + "parking_lot 0.12.5", "smallvec", "tracing", ] @@ -5474,7 +5502,7 @@ dependencies = [ "crossbeam-epoch", "crossbeam-utils", "equivalent", - "parking_lot", + "parking_lot 0.12.5", "portable-atomic", "smallvec", "tagptr", @@ -5646,6 +5674,7 @@ dependencies = [ "common", "pacaya", "permissionless", + "realtime", "rustls", "serde_json", "shasta", @@ -6310,6 +6339,17 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -6317,7 +6357,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", - "parking_lot_core", + "parking_lot_core 0.9.12", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", ] [[package]] @@ -6753,7 +6807,7 @@ dependencies = [ "fnv", "lazy_static", "memchr", - "parking_lot", + "parking_lot 0.12.5", "thiserror 2.0.18", ] @@ -6765,7 +6819,7 @@ checksum = "cf41c1a7c32ed72abe5082fb19505b969095c12da9f5732a4bc9878757fd087c" dependencies = [ "dtoa", "itoa", - "parking_lot", + "parking_lot 0.12.5", "prometheus-client-derive-encode", ] @@ -6925,7 +6979,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.5.10", + "socket2 0.6.2", "thiserror 2.0.18", "tokio", "tracing", @@ -6963,9 +7017,9 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -7112,6 +7166,51 @@ dependencies = [ "yasna", ] +[[package]] +name = "realtime" +version = "1.35.2" +dependencies = [ + "alethia-reth-consensus 0.6.0 (git+https://github.com/taikoxyz/alethia-reth.git?rev=637f7a150f72fe8d6cc5949a41aebb638a5305cf)", + "alloy", + "alloy-json-rpc", + "alloy-rlp", + "anyhow", + "async-trait", + "bindings", + "chrono", + "common", + "dotenvy", + "flate2", + "futures-util", + "hex", + "http", + "jsonrpsee", + "jsonwebtoken 10.3.0", + "mockito", + "pacaya", + "prometheus", + "protocol", + "reqwest 0.13.2", + "rpc", + "serde", + "serde_json", + "sled", + "tokio", + "tokio-util", + "tracing", + "tracing-subscriber 0.3.23", + "warp", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -7330,7 +7429,7 @@ dependencies = [ "alloy-primitives", "derive_more", "metrics 0.24.3", - "parking_lot", + "parking_lot 0.12.5", "pin-project", "rand 0.9.2", "reth-chainspec 1.11.3", @@ -7550,7 +7649,7 @@ dependencies = [ "discv5 0.10.2", "enr", "itertools 0.14.0", - "parking_lot", + "parking_lot 0.12.5", "rand 0.8.5", "reth-ethereum-forks 1.11.3", "reth-net-banlist 1.11.3", @@ -7963,7 +8062,7 @@ dependencies = [ "byteorder", "dashmap", "derive_more", - "parking_lot", + "parking_lot 0.12.5", "reth-mdbx-sys", "smallvec", "thiserror 2.0.18", @@ -8048,7 +8147,7 @@ dependencies = [ "futures", "itertools 0.14.0", "metrics 0.24.3", - "parking_lot", + "parking_lot 0.12.5", "pin-project", "rand 0.8.5", "rand 0.9.2", @@ -8435,7 +8534,7 @@ dependencies = [ "itertools 0.14.0", "metrics 0.24.3", "notify", - "parking_lot", + "parking_lot 0.12.5", "rayon", "reth-chain-state", "reth-chainspec 1.11.3", @@ -8728,7 +8827,7 @@ dependencies = [ "bitflags 2.11.0", "futures-util", "metrics 0.24.3", - "parking_lot", + "parking_lot 0.12.5", "pin-project", "rand 0.9.2", "reth-chain-state", @@ -8770,7 +8869,7 @@ dependencies = [ "auto_impl", "itertools 0.14.0", "metrics 0.24.3", - "parking_lot", + "parking_lot 0.12.5", "reth-execution-errors", "reth-metrics 1.11.3", "reth-primitives-traits 1.11.3", @@ -8813,7 +8912,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c4 dependencies = [ "alloy-primitives", "metrics 0.24.3", - "parking_lot", + "parking_lot 0.12.5", "reth-db-api", "reth-execution-errors", "reth-metrics 1.11.3", @@ -9604,7 +9703,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -9663,7 +9762,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs 0.26.11", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -9684,7 +9783,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs 1.0.6", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -10119,6 +10218,7 @@ dependencies = [ "bindings", "common", "hex", + "jsonrpsee", "mockito", "pacaya", "protocol", @@ -10127,6 +10227,7 @@ dependencies = [ "rpc", "serde", "serde_json", + "sled", "tokio", "tokio-util", "tracing", @@ -10210,6 +10311,22 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[package]] +name = "sled" +version = "0.34.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" +dependencies = [ + "crc32fast", + "crossbeam-epoch", + "crossbeam-utils", + "fs2", + "fxhash", + "libc", + "log", + "parking_lot 0.11.2", +] + [[package]] name = "smallvec" version = "1.15.1" @@ -10689,7 +10806,7 @@ dependencies = [ "getrandom 0.4.1", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -10818,7 +10935,7 @@ dependencies = [ "bytes", "libc", "mio", - "parking_lot", + "parking_lot 0.12.5", "pin-project-lite", "signal-hook-registry", "socket2 0.6.2", @@ -11713,7 +11830,7 @@ checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b" dependencies = [ "futures", "js-sys", - "parking_lot", + "parking_lot 0.12.5", "pin-utils", "slab", "wasm-bindgen", @@ -11813,7 +11930,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -12463,7 +12580,7 @@ dependencies = [ "futures", "log", "nohash-hasher", - "parking_lot", + "parking_lot 0.12.5", "pin-project", "rand 0.8.5", "static_assertions", @@ -12478,7 +12595,7 @@ dependencies = [ "futures", "log", "nohash-hasher", - "parking_lot", + "parking_lot 0.12.5", "pin-project", "rand 0.9.2", "static_assertions", diff --git a/Cargo.toml b/Cargo.toml index 60f0f6fd..3374d142 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "node", "pacaya", "permissionless", + "realtime", "shasta", "tools/p2p_boot_node", "urc", @@ -88,7 +89,9 @@ reqwest = { version = "0.13", default-features = true, features = ["json"] } rustls = { version = "0.23", default-features = true } secp256k1 = { version = "0.30", features = ["recovery", "rand"] } serde = { version = "1.0", default-features = false, features = ["derive"] } +sled = { version = "0.34", default-features = false } serde_json = { version = "1.0", default-features = false } +realtime = { path = "realtime" } shasta = { path = "shasta" } ssz_rs = { version = "0.9.0" } strum = { version = "0.27", features = ["derive"] } diff --git a/FETCH_REAL_TIME_PROOF.md b/FETCH_REAL_TIME_PROOF.md new file mode 100644 index 00000000..9f28c10b --- /dev/null +++ b/FETCH_REAL_TIME_PROOF.md @@ -0,0 +1,532 @@ +# Fetching a RealTime Proof from Raiko + +This document describes the complete mechanism for an external client to request and retrieve a **RealTime fork** proof from a Raiko prover server. + +--- + +## Table of Contents + +1. [Overview](#overview) +2. [Server Base URL & API Version](#server-base-url--api-version) +3. [Authentication](#authentication) +4. [Endpoint](#endpoint) +5. [Request Schema](#request-schema) +6. [Response Schema](#response-schema) +7. [Proof Lifecycle & Polling](#proof-lifecycle--polling) +8. [Task Reporting](#task-reporting) +9. [Error Handling](#error-handling) +10. [End-to-End Example](#end-to-end-example) + +--- + +## Overview + +The RealTime fork implements **atomic propose+prove**: one proposal per proof per transaction, with no aggregation. The client sends a single POST request containing the L2 block numbers, anchor info, signal slots, derivation sources, and prover configuration. The server performs a two-stage pipeline internally: + +1. **Guest input generation** — constructs the provable witness from L1/L2 state. +2. **Proof generation** — runs the selected prover backend (SGX, SP1, RISC0, etc.) on the witness. + +The response is **synchronous within a long-poll model**: the server returns immediately with either the completed proof or a status indicating work is in progress. If the proof is not ready, the client re-submits the same request to poll. + +--- + +## Server Base URL & API Version + +The RealTime endpoint lives under the **V3 API**. The V3 routes are also merged at the root, so both paths work: + +``` +POST {BASE_URL}/v3/proof/batch/realtime +POST {BASE_URL}/proof/batch/realtime +``` + +Where `{BASE_URL}` is the Raiko server address (e.g. `http://localhost:8080`). + +--- + +## Authentication + +Authentication is **optional** and depends on server configuration. + +### API Key (primary mechanism) + +If the server is started with `RAIKO_KEYS` set, all requests require an `X-API-KEY` header. + +``` +X-API-KEY: raiko_ +``` + +Keys are configured server-side via the `RAIKO_KEYS` environment variable as a JSON map of `{ "name": "key_value" }` pairs: + +```bash +RAIKO_KEYS='{"my-client":"raiko_abc123..."}' +``` + +**Rate limiting**: Default 600 requests/minute per key, configurable via `RAIKO_RATE_LIMIT`. + +**HTTP error codes for auth failures**: +| Code | Meaning | +|------|---------| +| 401 | Missing, invalid, or inactive API key | +| 429 | Rate limit exceeded | + +### JWT Bearer Token (fallback) + +If no API key store is configured but a JWT secret is set, the server falls back to `Authorization: Bearer ` validation. + +### No Auth + +If neither is configured, requests are accepted anonymously. + +--- + +## Endpoint + +``` +POST /v3/proof/batch/realtime +Content-Type: application/json +X-API-KEY: (if auth enabled) +``` + +--- + +## Request Schema + +```jsonc +{ + // === Required fields === + + // L2 block numbers covered by this proposal. + // Must be non-empty. + "l2_block_numbers": [100, 101, 102], + + // Proof backend. One of: "native", "sp1", "risc0", "sgx", "sgxgeth", "tdx", "azure_tdx" + // Special values: "zk_any" (server picks ZK prover), "sgx_any" (server picks SGX variant) + "proof_type": "sgx", + + // Highest L1 block number that the L2 derivation references. + "max_anchor_block_number": 19500000, + + // Hash of the parent proposal, obtained from the on-chain getLastProposalHash(). + // Use 0x0...0 (32-byte zero) for the first proposal after genesis. + "parent_proposal_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + + // Percentage of basefee paid to the coinbase address (0-100). + "basefee_sharing_pctg": 0, + + // === Optional fields (server defaults may apply) === + + // L2 network identifier. Must match a chain spec configured on the server. + "network": "taiko_mainnet", + + // L1 network identifier. + "l1_network": "ethereum", + + // EVM address of the prover (checksummed or lowercase hex). + "prover": "0xYourProverAddress", + + // Blob proof type. Defaults to "proof_of_equivalence". + "blob_proof_type": "proof_of_equivalence", + + // L1 signal slots to relay. Array of 32-byte hashes. Defaults to []. + // When non-empty, the on-chain signalSlotsHash = keccak256(abi.encode(signal_slots)). + // When empty, signalSlotsHash = bytes32(0). + "signal_slots": [], + + // Derivation sources for blob data. Defaults to []. + // Each source maps to the on-chain DerivationSource struct. + "sources": [ + { + "isForcedInclusion": false, + "blobSlice": { + "blobHashes": ["0x<32-byte-hash>"], + "offset": 0, + "timestamp": 1700000000 + } + } + ], + + // Previous finalized checkpoint. Null/omitted if none exists yet. + "checkpoint": { + "block_number": 99, + "block_hash": "0x<32-byte-hash>", + "state_root": "0x<32-byte-hash>" + }, + + // Prover-specific options. Keyed by prover backend name. + // Only the key matching your proof_type is used. + "sgx": null, + "sp1": null, + "risc0": null, + "native": null, + "sgxgeth": null, + "tdx": null, + "azure_tdx": null +} +``` + +### Field Details + +| Field | Type | Required | Description | +|---|---|---|---| +| `l2_block_numbers` | `u64[]` | Yes | L2 block numbers in this proposal. Must be non-empty. | +| `proof_type` | `string` | Yes | Prover backend: `native`, `sp1`, `risc0`, `sgx`, `sgxgeth`, `tdx`, `azure_tdx`, `zk_any`, `sgx_any` | +| `max_anchor_block_number` | `u64` | Yes | Highest L1 block the L2 derivation references | +| `parent_proposal_hash` | `bytes32` | Yes | Hash of the parent proposal from `getLastProposalHash()` | +| `basefee_sharing_pctg` | `u8` | Yes | Basefee sharing percentage (0-100) | +| `network` | `string` | No* | L2 network name (server default used if omitted) | +| `l1_network` | `string` | No* | L1 network name (server default used if omitted) | +| `prover` | `string` | No* | Prover EVM address (server default used if omitted) | +| `blob_proof_type` | `string` | No | Defaults to `"proof_of_equivalence"` | +| `signal_slots` | `bytes32[]` | No | L1 signal slots to relay. Defaults to `[]` | +| `sources` | `DerivationSource[]` | No | Blob derivation sources. Defaults to `[]` | +| `checkpoint` | `object \| null` | No | Previous finalized L2 checkpoint | + +\* These fields are required for proof generation but can be omitted if the server has defaults configured via its config file or command-line options. + +### `zk_any` and `sgx_any` Proof Types + +When you set `proof_type` to `"zk_any"` or `"sgx_any"`, the server **draws** (selects) a concrete prover backend based on internal ballot logic. If no prover is drawn, the response returns status `ZKAnyNotDrawn` — this is not an error, it means the server decided not to prove this request. The client should handle this gracefully. + +--- + +## Response Schema + +All responses use the V3 `Status` envelope, serialized with `"status"` as a discriminator tag. + +### Success — Proof Ready + +```json +{ + "status": "ok", + "proof_type": "sgx", + "data": { + "proof": "" + } +} +``` + +The `proof` field contains the proof bytes/string as produced by the prover backend. For SGX this is typically a quote; for ZK provers it's the serialized proof. + +### Success — Work In Progress + +Returned when the proof is still being generated. **Re-submit the same request to poll.** + +```json +{ + "status": "ok", + "proof_type": "sgx", + "data": { + "status": "WorkInProgress" + } +} +``` + +### Success — Registered + +Returned when the task has been registered but not yet started (e.g., guest input generation is still running). + +```json +{ + "status": "ok", + "proof_type": "sgx", + "data": { + "status": "Registered" + } +} +``` + +### Success — ZK Any Not Drawn + +Returned when `proof_type` was `"zk_any"` or `"sgx_any"` and the server decided not to prove this request. + +```json +{ + "status": "ok", + "proof_type": "native", + "data": { + "status": "ZKAnyNotDrawn" + } +} +``` + +### Error + +```json +{ + "status": "error", + "error": "task_failed", + "message": "Human-readable error description" +} +``` + +### Response Status Summary + +| `status` | `data` variant | Meaning | Client Action | +|---|---|---|---| +| `"ok"` | `{ "proof": "..." }` | Proof is ready | Extract and use the proof | +| `"ok"` | `{ "status": "Registered" }` | Task queued, guest input generating | Wait and re-submit same request | +| `"ok"` | `{ "status": "WorkInProgress" }` | Proof being generated | Wait and re-submit same request | +| `"ok"` | `{ "status": "Cancelled" }` | Task was cancelled | Do not retry | +| `"ok"` | `{ "status": "ZKAnyNotDrawn" }` | `zk_any`/`sgx_any` not selected | Handle gracefully; no proof produced | +| `"error"` | N/A | Failure | Inspect `error` and `message` fields | + +--- + +## Proof Lifecycle & Polling + +The RealTime endpoint uses a **re-submit polling** model, not a separate status endpoint. The flow: + +``` +Client Server + | | + |-- POST /v3/proof/batch/realtime -------->| + | |-- Stage 1: generate guest input + |<-- { "status":"ok", data.status: | + | "Registered" } --------------------| + | | + | (wait 5-10 seconds) | + | | + |-- POST /v3/proof/batch/realtime -------->| (same request body) + | |-- Stage 1 complete, Stage 2: prove + |<-- { "status":"ok", data.status: | + | "WorkInProgress" } -----------------| + | | + | (wait 5-30 seconds) | + | | + |-- POST /v3/proof/batch/realtime -------->| (same request body) + | |-- Proof ready + |<-- { "status":"ok", | + | data.proof: "0x..." } --------------| +``` + +**Key points:** +- Re-submit the **identical** request body each time. The server deduplicates by request key. +- The server internally manages the two-stage pipeline (guest input → proof). +- There is no separate GET endpoint for RealTime proof status. +- Recommended polling interval: 5-30 seconds depending on proof type (native is fast, ZK provers are slow). + +--- + +## Task Reporting + +To check the status of all in-flight tasks (including RealTime), use the report endpoint: + +``` +GET /v3/proof/report +X-API-KEY: +``` + +Response is an array of task reports: + +```json +[ + { + "descriptor": { + "RealTimeGuestInput": { + "l2_block_numbers": [100, 101], + "l1_network": "ethereum", + "l2_network": "taiko_mainnet", + "parent_proposal_hash": "0x..." + } + }, + "status": "WorkInProgress" + }, + { + "descriptor": { + "RealTimeProof": { + "l2_block_numbers": [100, 101], + "l1_network": "ethereum", + "l2_network": "taiko_mainnet", + "parent_proposal_hash": "0x...", + "proof_system": "sgx", + "prover": "0x..." + } + }, + "status": "Success" + } +] +``` + +RealTime tasks produce two descriptor types in reports: +- `RealTimeGuestInput` — the witness generation stage +- `RealTimeProof` — the actual proof generation stage + +--- + +## Error Handling + +### HTTP-Level Errors + +| HTTP Code | Cause | +|-----------|-------| +| 400 | Invalid request: missing required fields, bad field types, empty `l2_block_numbers` | +| 401 | Authentication failure (missing/invalid API key) | +| 429 | Rate limit exceeded | +| 500 | Internal server error (prover crash, I/O failure, etc.) | +| 503 | Server capacity full or system paused | + +### Application-Level Errors (in response JSON) + +These return HTTP 200 but with `"status": "error"`: + +```json +{ + "status": "error", + "error": "task_failed", + "message": "Task failed with status: AnyhowError(\"RPC timeout\")" +} +``` + +Common error messages: +- `"l2_block_numbers is empty"` — validation failure +- `"Missing network"` / `"Missing prover"` — required field not provided and no server default +- `"Invalid proof_type"` — unrecognized proof type string +- `"Feature not supported: "` — server not compiled with that prover backend + +--- + +## End-to-End Example + +### 1. Health Check + +```bash +curl http://localhost:8080/v3/health +# Expected: 200 OK +``` + +### 2. Submit RealTime Proof Request + +```bash +curl -X POST http://localhost:8080/v3/proof/batch/realtime \ + -H "Content-Type: application/json" \ + -H "X-API-KEY: raiko_your_key_here" \ + -d '{ + "l2_block_numbers": [100, 101, 102], + "proof_type": "sgx", + "network": "taiko_mainnet", + "l1_network": "ethereum", + "prover": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "max_anchor_block_number": 19500000, + "parent_proposal_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "basefee_sharing_pctg": 0, + "signal_slots": [], + "sources": [], + "checkpoint": null, + "blob_proof_type": "proof_of_equivalence", + "sgx": null + }' +``` + +### 3. Poll Until Proof Ready + +```bash +# Response will be one of: +# { "status": "ok", "proof_type": "sgx", "data": { "status": "Registered" } } +# { "status": "ok", "proof_type": "sgx", "data": { "status": "WorkInProgress" } } +# { "status": "ok", "proof_type": "sgx", "data": { "proof": "0x..." } } + +# Re-submit the same request body until data.proof is present. +``` + +### 4. Pseudocode Client Loop + +```python +import requests, time + +RAIKO_URL = "http://localhost:8080/v3/proof/batch/realtime" +HEADERS = { + "Content-Type": "application/json", + "X-API-KEY": "raiko_your_key_here", +} + +payload = { + "l2_block_numbers": [100, 101, 102], + "proof_type": "sgx", + "network": "taiko_mainnet", + "l1_network": "ethereum", + "prover": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "max_anchor_block_number": 19500000, + "parent_proposal_hash": "0x" + "00" * 32, + "basefee_sharing_pctg": 0, + "signal_slots": [], + "sources": [], + "checkpoint": None, + "blob_proof_type": "proof_of_equivalence", +} + +while True: + resp = requests.post(RAIKO_URL, json=payload, headers=HEADERS) + body = resp.json() + + if body["status"] == "error": + raise Exception(f"Proof failed: {body['message']}") + + data = body["data"] + + # Proof returned + if "proof" in data: + proof = data["proof"] + print(f"Proof received: {proof[:40]}...") + break + + # zk_any/sgx_any not drawn — no proof will be produced + if data.get("status") == "ZKAnyNotDrawn": + print("Prover not drawn for this request") + break + + # Still in progress + status = data.get("status", "Unknown") + print(f"Status: {status}, polling again...") + time.sleep(10) +``` + +--- + +## Appendix: DerivationSource Schema + +The `sources` array contains objects matching the on-chain `DerivationSource` struct: + +```jsonc +{ + // Whether this source is from a forced inclusion. + // Always false for RealTimeInbox proposals. + "isForcedInclusion": false, + + // Blob slice referencing the transaction data. + "blobSlice": { + // Array of 32-byte blob hashes (versioned hashes from the blob transaction). + "blobHashes": ["0x01<...>"], + // Byte offset within the blob where this source's data begins. + "offset": 0, + // Timestamp associated with the blob. + "timestamp": 1700000000 + } +} +``` + +## Appendix: Checkpoint Schema + +```jsonc +{ + // L2 block number of the checkpoint. + "block_number": 99, + // Block hash at that L2 block. + "block_hash": "0x<32-byte-hex>", + // State root at that L2 block. + "state_root": "0x<32-byte-hex>" +} +``` + +## Appendix: Supported Proof Types + +| Value | Backend | Description | +|---|---|---| +| `"native"` | Native | Block construction + equality check (no cryptographic proof) | +| `"sp1"` | SP1 | Succinct SP1 zero-knowledge prover | +| `"risc0"` | RISC0 | RISC Zero zero-knowledge prover | +| `"sgx"` | Intel SGX | Trusted execution environment proof | +| `"sgxgeth"` | SGX + Geth | SGX with Geth execution client | +| `"tdx"` | Intel TDX | Trust Domain Extensions proof | +| `"azure_tdx"` | Azure TDX | Azure Confidential VM (TDX) proof | +| `"zk_any"` | Server-selected ZK | Server picks between SP1/RISC0 via ballot | +| `"sgx_any"` | Server-selected SGX | Server picks between SGX/SGXGeth via ballot | diff --git a/PROTOCOL_MIGRATION_REAL_TIME_FORK.md b/PROTOCOL_MIGRATION_REAL_TIME_FORK.md new file mode 100644 index 00000000..45f8b95b --- /dev/null +++ b/PROTOCOL_MIGRATION_REAL_TIME_FORK.md @@ -0,0 +1,454 @@ +# Real-Time Inbox: Technical Reference + +> Documents the shift from the standard two-phase `Inbox` (Shasta) to the single-phase +> `RealTimeInbox` for real-time proving. + +--- + +## 1. Architectural Shift + +### Standard Inbox — Two-Phase Model + +``` +Phase 1: propose(lookahead, data) → Store proposal hash in ring buffer, emit Proposed +Phase 2: prove(data, proof) → Verify proof for a BATCH of proposals, finalize state +``` + +Proposals accumulate on-chain. A prover later submits a single proof covering a contiguous range +of proposals `[N..M]`. The contract maintains `CoreState`, a ring buffer of proposal hashes, +forced inclusion queues, and bond balances. + +### RealTimeInbox — Atomic Single-Phase Model + +``` +propose(data, checkpoint, proof) → Build proposal + Verify proof + Finalize (one tx) +``` + +Each proposal is proven immediately in the same transaction. Only `bytes32 lastProposalHash` is +persisted. No batching — exactly one proposal per proof. No bonds, forced inclusions, proposer +checks, prover whitelist, or ring buffer. + +The prover must execute L2 blocks and generate the ZK proof **before** submitting the +transaction. + +--- + +## 2. Type Changes + +### 2.1 Config + +**Inbox** `IInbox.Config` — 17 fields: + +```solidity +struct Config { + address proofVerifier; + address proposerChecker; // REMOVED + address proverWhitelist; // REMOVED + address signalService; + address bondToken; // REMOVED + uint64 minBond; // REMOVED + uint64 livenessBond; // REMOVED + uint48 withdrawalDelay; // REMOVED + uint48 provingWindow; // REMOVED + uint48 permissionlessProvingDelay; // REMOVED + uint48 maxProofSubmissionDelay; // REMOVED + uint48 ringBufferSize; // REMOVED + uint8 basefeeSharingPctg; + uint16 forcedInclusionDelay; // REMOVED + uint64 forcedInclusionFeeInGwei; // REMOVED + uint64 forcedInclusionFeeDoubleThreshold; // REMOVED + uint8 permissionlessInclusionMultiplier; // REMOVED +} +``` + +**RealTimeInbox** `IRealTimeInbox.Config` — 3 fields: + +```solidity +struct Config { + address proofVerifier; // SurgeVerifier address + address signalService; // SignalService address + uint8 basefeeSharingPctg; // % of basefee paid to coinbase +} +``` + +### 2.2 ProposeInput + +**Inbox** `IInbox.ProposeInput`: + +```solidity +struct ProposeInput { + uint48 deadline; // REMOVED + LibBlobs.BlobReference blobReference; + uint16 numForcedInclusions; // REMOVED +} +``` + +**RealTimeInbox** `IRealTimeInbox.ProposeInput`: + +```solidity +struct ProposeInput { + LibBlobs.BlobReference blobReference; + bytes32[] signalSlots; // NEW — L1 signal slots to relay + uint48 maxAnchorBlockNumber; // NEW — highest L1 anchor block +} +``` + +- `signalSlots` is now a first-class input. Each slot is verified via + `_signalService.isSignalSent(slot)` and hashed into the proposal. +- `maxAnchorBlockNumber` must satisfy `blockhash(maxAnchorBlockNumber) != 0` + (within last 256 L1 blocks). The corresponding `maxAnchorBlockHash` is read on-chain via + `blockhash()` and included in the proposal. These new max anchor block values will be used + for verifying anchor linkage — the L2 node uses them to verify that anchor transactions + reference a valid, recent L1 block. + +### 2.3 Proposal + +**Inbox** `IInbox.Proposal` — stored in ring buffer: + +```solidity +struct Proposal { + uint48 id; // REMOVED + uint48 timestamp; // REMOVED + uint48 endOfSubmissionWindowTimestamp; // REMOVED + address proposer; // REMOVED + bytes32 parentProposalHash; + uint48 originBlockNumber; // REMOVED + bytes32 originBlockHash; // REMOVED + uint8 basefeeSharingPctg; + DerivationSource[] sources; + bytes32 signalSlotsHash; +} +``` + +**RealTimeInbox** `IRealTimeInbox.Proposal` — **transient, never stored** (only hashed): + +```solidity +struct Proposal { + uint48 maxAnchorBlockNumber; // NEW — highest L1 anchor block number + bytes32 maxAnchorBlockHash; // NEW — blockhash(maxAnchorBlockNumber) + uint8 basefeeSharingPctg; + IInbox.DerivationSource[] sources; // Reuses IInbox.DerivationSource + bytes32 signalSlotsHash; +} +``` + +- Standalone — no parent linkage. State continuity is enforced via `Commitment.lastBlockHash`. +- No sequential `id` — proposals identified by hash only. +- No `timestamp`, `proposer`, or `endOfSubmissionWindowTimestamp`. +- `originBlockNumber`/`originBlockHash` replaced by `maxAnchorBlockNumber`/`maxAnchorBlockHash`. + The semantics shift from "L1 block the proposal was made in" to "highest L1 block the L2 + derivation can reference." The new max anchor block values will be used for verifying anchor + linkage — the L2 execution layer uses `maxAnchorBlockNumber` and `maxAnchorBlockHash` to + validate that anchor transactions in L2 blocks correctly reference an L1 block at or before + this height, ensuring L1-L2 state consistency. + +### 2.4 Commitment (Critical for Provers) + +**Inbox** `IInbox.Commitment` — covers a batch: + +```solidity +struct Commitment { + uint48 firstProposalId; + bytes32 firstProposalParentBlockHash; + bytes32 lastProposalHash; + address actualProver; + uint48 endBlockNumber; + bytes32 endStateRoot; + Transition[] transitions; // Per-proposal: { proposer, timestamp, blockHash } +} +``` + +**RealTimeInbox** `IRealTimeInbox.Commitment` — covers exactly one proposal: + +```solidity +struct Commitment { + bytes32 proposalHash; + bytes32 lastFinalizedBlockHash; // Block hash of last finalized L2 block (proof starting state) + ICheckpointStore.Checkpoint checkpoint; // { blockNumber, blockHash, stateRoot } +} +``` + +No batch support. No `actualProver`, no `Transition[]`. The `lastFinalizedBlockHash` binds the +proof to the correct starting state (must match `lastFinalizedBlockHash` on-chain). The checkpoint +contains the finalized L2 state for the single proposal. + +### 2.5 Removed Types + +| Type | Purpose | +| -------------------- | ----------------------------------------------------------------- | +| `CoreState` | Tracked nextProposalId, lastFinalizedProposalId, timestamps, etc. | +| `Transition` | Per-proposal transition data in batch proofs | +| `ProveInput` | Wrapper for Commitment in `prove()` | +| `ProvedEventPayload` | Event payload struct | + +### 2.6 Shared Types (Unchanged) + +```solidity +// IInbox — reused by RealTimeInbox +struct DerivationSource { + bool isForcedInclusion; // Always false in RealTimeInbox + LibBlobs.BlobSlice blobSlice; +} + +// LibBlobs +struct BlobReference { uint16 blobStartIndex; uint16 numBlobs; uint24 offset; } +struct BlobSlice { bytes32[] blobHashes; uint24 offset; uint48 timestamp; } + +// ICheckpointStore +struct Checkpoint { uint48 blockNumber; bytes32 blockHash; bytes32 stateRoot; } +``` + +--- + +## 3. Function Signatures + +### Activation + +```solidity +// Inbox +function activate(bytes32 _lastPacayaBlockHash) external onlyOwner; +// Sets up CoreState, stores genesis proposal hash in ring buffer slot 0 + +// RealTimeInbox +function activate(bytes32 _genesisBlockHash) external onlyOwner; +// Sets lastFinalizedBlockHash = _genesisBlockHash. Can only be called once. +``` + +### Propose + +```solidity +// Inbox — proposal only, no proof +function propose(bytes calldata _lookahead, bytes calldata _data) external; + +// RealTimeInbox — atomic propose + prove +function propose( + bytes calldata _data, // abi.encode(IRealTimeInbox.ProposeInput) + ICheckpointStore.Checkpoint calldata _checkpoint, + bytes calldata _proof +) external; +``` + +### Prove (Removed) + +```solidity +// Inbox +function prove(bytes calldata _data, bytes calldata _proof) external; + +// RealTimeInbox — does not exist. Proving is embedded in propose(). +``` + +### Removed Function Groups + +- **Bond management**: `deposit`, `depositTo`, `withdraw`, `requestWithdrawal`, `cancelWithdrawal`, `getBond` +- **Forced inclusions**: `saveForcedInclusion`, `getCurrentForcedInclusionFee`, `getForcedInclusions`, `getForcedInclusionState` + +### State Queries + +```solidity +// Inbox +function getCoreState() external view returns (CoreState memory); +function getProposalHash(uint256 _proposalId) external view returns (bytes32); + +// RealTimeInbox — replaces both with: +function getLastFinalizedBlockHash() external view returns (bytes32); +``` + +### Encoding Helpers + +RealTimeInbox uses plain `abi.encode`/`abi.decode` (no `LibCodec` or `LibHashOptimized`): + +```solidity +function encodeProposeInput(ProposeInput calldata) public pure returns (bytes memory); +function decodeProposeInput(bytes calldata) public pure returns (ProposeInput memory); +function hashProposal(Proposal memory) public pure returns (bytes32); // keccak256(abi.encode(...)) +function hashCommitment(Commitment memory) public pure returns (bytes32); // keccak256(abi.encode(...)) +function hashSignalSlots(bytes32[] memory) public pure returns (bytes32); // keccak256(abi.encode(...)) +``` + +--- + +## 4. On-Chain State + +**Inbox**: + +```solidity +uint48 public activationTimestamp; +CoreState internal _coreState; // 2 slots +mapping(uint256 bufferSlot => bytes32 proposalHash) _proposalHashes; // ring buffer +LibForcedInclusion.Storage _forcedInclusionStorage; // 2 slots +LibBonds.Storage _bondStorage; +``` + +**RealTimeInbox**: + +```solidity +bytes32 public lastFinalizedBlockHash; // 1 slot — block hash of last finalized L2 block +``` + +--- + +## 5. Events + +**Inbox** emits separate events for proposing and proving: + +```solidity +event Proposed( + uint48 indexed id, address indexed proposer, + bytes32 parentProposalHash, uint48 endOfSubmissionWindowTimestamp, + uint8 basefeeSharingPctg, DerivationSource[] sources, bytes32 signalSlotsHash +); + +event Proved( + uint48 firstProposalId, uint48 firstNewProposalId, + uint48 lastProposalId, address indexed actualProver +); +``` + +**RealTimeInbox** emits a single combined event: + +```solidity +event ProposedAndProved( + bytes32 indexed proposalHash, + bytes32 lastFinalizedBlockHash, + uint48 maxAnchorBlockNumber, + uint8 basefeeSharingPctg, + IInbox.DerivationSource[] sources, + bytes32[] signalSlots, + ICheckpointStore.Checkpoint checkpoint +); +``` + +- Indexed by `proposalHash` instead of sequential `id`. +- `lastFinalizedBlockHash` replaces `parentProposalHash` — the block hash of the last finalized L2 block. +- Includes the finalized `Checkpoint` directly. +- No `proposer` or `actualProver` field. + +--- + +## 6. Proof Verification + +Both contracts call `IProofVerifier.verifyProof(uint256, bytes32, bytes)`. The interface is +unchanged. + +**Inbox**: + +``` +proposalAge = block.timestamp - transitions[offset].timestamp +commitmentHash = LibHashOptimized.hashCommitment(commitment) +verifyProof(proposalAge, commitmentHash, proof) +``` + +**RealTimeInbox**: + +``` +proposalAge = 0 // always 0 +commitmentHash = keccak256(abi.encode(commitment)) // plain abi.encode +verifyProof(0, commitmentHash, proof) +``` + +### Commitment Hash Reconstruction + +For off-chain reconstruction of the commitment hash: + +``` +proposalHash = keccak256(abi.encode( + uint48 maxAnchorBlockNumber, // padded to 32 bytes by abi.encode + bytes32 maxAnchorBlockHash, + uint8 basefeeSharingPctg, // padded to 32 bytes by abi.encode + IInbox.DerivationSource[] sources, // dynamic array encoding + bytes32 signalSlotsHash +)) + +commitmentHash = keccak256(abi.encode( + bytes32 proposalHash, + bytes32 lastFinalizedBlockHash, // last finalized L2 block hash + uint48 checkpoint.blockNumber, // padded to 32 bytes by abi.encode + bytes32 checkpoint.blockHash, + bytes32 checkpoint.stateRoot +)) +``` + +### Signal Slots Hash + +```solidity +signalSlotsHash = bytes32(0) // if empty +signalSlotsHash = keccak256(abi.encode(signalSlots)) // if non-empty (bytes32[]) +``` + +--- + +## 7. L2 Anchor Integration — Signal Slot Relay + +`signalSlots` provided in `ProposeInput` must be relayed to L2 so that nodes can verify L1→L2 +cross-chain messages without a separate proof. The relay happens through the L2 anchor +transaction of the **first block** in the batch. + +### Anchor Function + +The standard `anchorV4` is replaced by `anchorV4WithSignalSlots`: + +```solidity +// Anchor.sol (L2) + +// Standard — no signal relay +function anchorV4(ICheckpointStore.Checkpoint calldata _checkpoint) external; + +// Real-time inbox — relays signal slots in the first block's anchor tx +function anchorV4WithSignalSlots( + ICheckpointStore.Checkpoint calldata _checkpoint, + bytes32[] calldata _signalSlots +) external; +``` + +### Placement Rule + +Only the **first block** of a batch carries all signal slots. Subsequent blocks in the same +batch call `anchorV4WithSignalSlots` with an empty `_signalSlots` array (or `anchorV4`). + +``` +Batch (from one propose() call) +├── Block 0 — anchorV4WithSignalSlots(checkpoint, signalSlots) ← all slots here +├── Block 1 — anchorV4WithSignalSlots(checkpoint, []) +└── Block N — anchorV4WithSignalSlots(checkpoint, []) +``` + +### What the Anchor Does with Signal Slots + +```solidity +if (_signalSlots.length > 0) { + ISignalService(address(checkpointStore)).setSignalsReceived(_signalSlots); +} +``` + +Each slot is marked as received in the `SignalService`, making L1 signals immediately +consumable on L2 without a merkle proof — consistent with the real-time proving model where +L1 state is already finalized before the L2 block is executed. + +### Relationship to `signalSlotsHash` + +The same `signalSlots` array that is passed to `anchorV4WithSignalSlots` on L2 is also hashed +into the proposal on L1: + +``` +L1 propose(): signalSlotsHash = keccak256(abi.encode(signalSlots)) → committed in proposalHash +L2 anchor(): anchorV4WithSignalSlots(checkpoint, signalSlots) → signals set in SignalService +``` + +The ZK proof covers both sides, ensuring the same set of slots is committed on L1 and +activated on L2. + +--- + +## 8. Removed Features Summary + +| Feature | Impact | +| ---------------------------------- | ----------------------------------------- | +| Batch proving | One proposal per proof; no `Transition[]` | +| Ring buffer | No historical proposal hash queries | +| Bonds | No economic security from proposer stakes | +| Forced inclusions | No censorship resistance mechanism | +| Proposer checker / lookahead | Anyone can propose | +| Prover whitelist | Anyone can prove | +| Proving window / liveness slashing | No deadlines or slashing | +| One-per-block limit | Multiple proposals per L1 block allowed | +| Transaction deadline | No `deadline` field in input | diff --git a/SURGE_POC.md b/SURGE_POC.md new file mode 100644 index 00000000..99b05e29 --- /dev/null +++ b/SURGE_POC.md @@ -0,0 +1,161 @@ +# Surge Real-Time Composability POC + +This document explains the end-to-end flow of the Surge real-time composability proof-of-concept. It covers how a UserOp enters the system, gets simulated, triggers cross-chain calls, and lands on L1 — all within a single proposal. + +## Architecture Overview + +``` +Client Node L1 Chain + │ │ │ + │── surge_sendUserOp ─▶│ │ + │◀── returns op ID ────│ │ + │ │ │ + │ │── simulate UserOp (trace) ───▶│ + │ │◀── MessageSent + SignalSent ──│ + │ │ │ + │ │── build L2 block ──┐ │ + │ │ (anchor with │ │ + │ │ signal slots + │ │ + │ │ bridge processMessage) │ + │ │◀───────────────────┘ │ + │ │ │ + │ │── detect L2→L1 calls │ + │ │ │ + │ │── build L1 multicall: ────────▶│ + │ │ 1. execute UserOp │ + │ │ 2. propose batch (blobs) │ + │ │ 3. relay L1 call │ + │ │ │ + │── surge_userOpStatus▶│ │ + │◀── Executed ─────────│ │ +``` + +## Key Idea + +A single L1 multicall transaction atomically: +1. **Executes the UserOp** on L1 (e.g. bridge deposit) +2. **Proposes the batch** containing L2 blocks that already processed the L2 side of that bridge call +3. **Relays any L2→L1 callbacks** from those L2 blocks + +The L2 blocks can process the bridge message immediately because the anchor transaction sets the signal slots ahead of time — before the L1 tx even lands. If the L1 multicall reverts, the L2 blocks are also invalid, maintaining consistency. + +## Flow Step by Step + +### 1. UserOp Submission (RPC) + +A client sends a UserOp via the `surge_sendUserOp` JSON-RPC endpoint. + +- **File**: [bridge_handler.rs:131](shasta/src/node/proposal_manager/bridge_handler.rs#L131) +- The RPC server assigns a unique ID, persists `Pending` status to a [sled store](shasta/src/node/proposal_manager/bridge_handler.rs#L37), and pushes the UserOp into an mpsc channel +- Returns the ID immediately so the client can poll status + +```jsonc +// Request +{ "method": "surge_sendUserOp", "params": [{ "submitter": "0x...", "calldata": "0x..." }] } +// Response +{ "result": 1 } // UserOp ID +``` + +**Status polling** via [`surge_userOpStatus`](shasta/src/node/proposal_manager/bridge_handler.rs#L159): +```jsonc +{ "method": "surge_userOpStatus", "params": [1] } +// Response cycles through: Pending → Processing { tx_hash } → Executed / Rejected { reason } +``` + +### 2. UserOp Simulation on L1 + +During block building, the [BatchManager](shasta/src/node/proposal_manager/mod.rs#L40) polls the BridgeHandler for pending UserOps. + +- **Entry**: [add_pending_l2_call_to_draft_block](shasta/src/node/proposal_manager/mod.rs#L293) +- **Calls**: [next_user_op_and_l2_call](shasta/src/node/proposal_manager/bridge_handler.rs#L210) +- **Which calls**: [find_message_and_signal_slot (L1)](shasta/src/l1/execution_layer.rs#L380) + +The L1 execution layer simulates the UserOp using `debug_trace_call` with a call tracer. It [recursively collects logs](shasta/src/l1/execution_layer.rs#L359) from the call tree looking for: +- **`MessageSent`** — the bridge message (from the Bridge contract) +- **`SignalSent`** — the signal slot (from the SignalService contract) + +If both are found, the UserOp is valid. If not, it's [rejected immediately](shasta/src/node/proposal_manager/bridge_handler.rs#L233). + +### 3. L2 Block Construction + +With a valid UserOp, the system builds an L2 block containing: + +1. **Anchor transaction** with signal slots — [construct_anchor_tx](shasta/src/l2/execution_layer.rs#L86) calls `anchorV4WithSignalSlots(checkpoint, signalSlots)`. This sets the signal slots on L2 so the bridge message can be validated immediately. + +2. **Bridge processMessage transaction** — [construct_l2_call_tx](shasta/src/l2/execution_layer.rs#L295) builds a signed tx calling `bridge.processMessage(message, proof)` on L2. + +Both are inserted into the draft block in [add_draft_block_to_proposal](shasta/src/node/proposal_manager/mod.rs#L331), which also records the UserOp and signal slot on the [Proposal](shasta/src/node/proposal_manager/proposal.rs#L16). + +After the L2 block is produced, the system [detects any L2→L1 calls](shasta/src/node/proposal_manager/mod.rs#L379) by querying L2 logs via [find_message_and_signal_slot (L2)](shasta/src/l2/execution_layer.rs#L357). Found calls get a signed proof and are added as [L1Call](shasta/src/node/proposal_manager/bridge_handler.rs#L78) to the proposal. + +### 4. L1 Multicall Submission + +When the batch is ready, [try_submit_oldest_batch](shasta/src/node/proposal_manager/batch_builder.rs#L346) triggers submission. + +The [ProposalTxBuilder](shasta/src/l1/proposal_tx_builder.rs#L33) assembles the multicall in [build_propose_blob](shasta/src/l1/proposal_tx_builder.rs#L99): + +| Order | Call | Purpose | +|-------|------|---------| +| 1 | [build_user_op_call](shasta/src/l1/proposal_tx_builder.rs#L166) | Execute the UserOp on L1 (e.g. bridge deposit) | +| 2 | `build_propose_call` | Propose the batch with compressed block data as blobs | +| 3 | [build_l1_call_call](shasta/src/l1/proposal_tx_builder.rs#L241) | Relay L2→L1 bridge callback with signed proof | + +All three are bundled into one multicall tx sent to the `proposer_multicall` contract. This is atomic — if any call fails, all revert. + +The tx is sent via [send_batch_to_l1](shasta/src/l1/execution_layer.rs#L185) which passes it to the [TransactionMonitor](common/src/shared/transaction_monitor.rs#L41). + +### 5. Transaction Monitoring & Status Updates + +The [TransactionMonitor](common/src/shared/transaction_monitor.rs#L97) handles gas bumping, resubmission, and confirmation tracking. Two oneshot channels notify the batch builder: + +1. **tx_hash_notifier** — fires when the tx is first sent → status becomes `Processing { tx_hash }` +2. **tx_result_notifier** — fires when the tx is confirmed or fails → status becomes `Executed` or `Rejected` + +This is orchestrated by a [background task](shasta/src/node/proposal_manager/batch_builder.rs#L346) spawned during submission. + +### 6. Main Loop + +The [Node](shasta/src/node/mod.rs#L34) runs a preconfirmation loop via [entrypoint](shasta/src/node/mod.rs#L113). Each tick of [main_block_preconfirmation_step](shasta/src/node/mod.rs#L157): + +1. Checks if the node is the active preconfer +2. Checks if there are pending transactions OR [pending UserOps](shasta/src/node/proposal_manager/mod.rs#L283) +3. Calls [preconfirm_block](shasta/src/node/proposal_manager/mod.rs#L130) to build a block +4. Calls [try_submit_oldest_batch](shasta/src/node/proposal_manager/mod.rs#L104) to submit when ready + +## Key Files + +| File | Role | +|------|------| +| [bridge_handler.rs](shasta/src/node/proposal_manager/bridge_handler.rs) | RPC server, UserOp intake, L1/L2 call detection, status store | +| [mod.rs](shasta/src/node/proposal_manager/mod.rs) | BatchManager — orchestrates block building with bridge data | +| [batch_builder.rs](shasta/src/node/proposal_manager/batch_builder.rs) | Accumulates blocks into proposals, handles submission + status tracking | +| [proposal.rs](shasta/src/node/proposal_manager/proposal.rs) | Proposal struct with Surge fields (user_ops, signal_slots, l1_calls, checkpoint) | +| [execution_layer.rs (L1)](shasta/src/l1/execution_layer.rs) | L1 simulation (trace), batch submission | +| [execution_layer.rs (L2)](shasta/src/l2/execution_layer.rs) | L2 tx construction (anchor, bridge call), L2 event detection | +| [proposal_tx_builder.rs](shasta/src/l1/proposal_tx_builder.rs) | Assembles the L1 multicall (UserOp + propose + L1 call) | +| [transaction_monitor.rs](common/src/shared/transaction_monitor.rs) | L1 tx lifecycle — send, bump gas, confirm, notify | +| [node/mod.rs](shasta/src/node/mod.rs) | Main preconfirmation loop | +| [lib.rs](shasta/src/lib.rs) | Node startup and initialization | + +## Configuration + +| Env Var / Constant | Where | What | +|---|---|---| +| Bridge RPC address | [mod.rs:77](shasta/src/node/proposal_manager/mod.rs#L77) | Hardcoded `127.0.0.1:4545` | +| Status DB path | [bridge_handler.rs:116](shasta/src/node/proposal_manager/bridge_handler.rs#L116) | Hardcoded `data/user_op_status` | +| L1 call proof signer | [bridge_handler.rs:193](shasta/src/node/proposal_manager/bridge_handler.rs#L193) | Anvil key #0 (POC only) | +| Preconf heartbeat | Node config | `PRECONF_HEARTBEAT_MS` (default 2000ms) | + +## Tweaking the POC + +**Change what the UserOp does**: Modify [build_user_op_call](shasta/src/l1/proposal_tx_builder.rs#L166) — currently it just forwards `submitter` + `calldata` as-is into the multicall. + +**Change how L2 processes the bridge message**: Modify [construct_l2_call_tx](shasta/src/l2/execution_layer.rs#L295) — currently calls `bridge.processMessage()`. + +**Change the multicall order or add calls**: Modify [build_propose_blob](shasta/src/l1/proposal_tx_builder.rs#L99) — the order of calls in the multicall array. + +**Change how L1 simulation extracts events**: Modify [find_message_and_signal_slot (L1)](shasta/src/l1/execution_layer.rs#L380) — change which events are extracted from the trace. + +**Change status persistence**: Swap out [UserOpStatusStore](shasta/src/node/proposal_manager/bridge_handler.rs#L37) — currently backed by sled at `data/user_op_status`. + +**Change the RPC port**: Update the socket address in [BatchManager::new](shasta/src/node/proposal_manager/mod.rs#L77). diff --git a/common/src/fork_info/config.rs b/common/src/fork_info/config.rs index 68849d62..8978202d 100644 --- a/common/src/fork_info/config.rs +++ b/common/src/fork_info/config.rs @@ -16,6 +16,7 @@ impl Default for ForkInfoConfig { Duration::from_secs(0), // Pacaya Duration::from_secs(99999999999), // Shasta Duration::from_secs(99999999999), // Permissionless + Duration::from_secs(99999999999), // Realtime ], fork_switch_transition_period: Duration::from_secs(15), } @@ -29,6 +30,7 @@ impl From<&Config> for ForkInfoConfig { Fork::Pacaya => Duration::from_secs(config.pacaya_timestamp_sec), Fork::Shasta => Duration::from_secs(config.shasta_timestamp_sec), Fork::Permissionless => Duration::from_secs(config.permissionless_timestamp_sec), + Fork::Realtime => Duration::from_secs(99999999999), // Only activated via FORK=realtime }) .collect(); Self { diff --git a/common/src/fork_info/fork.rs b/common/src/fork_info/fork.rs index ea5ba343..0e7f85f7 100644 --- a/common/src/fork_info/fork.rs +++ b/common/src/fork_info/fork.rs @@ -1,4 +1,5 @@ use std::fmt::{Display, Formatter, Result as FmtResult}; +use std::str::FromStr; use strum::{EnumIter, IntoEnumIterator}; #[derive(Clone, Debug, PartialEq, Eq, EnumIter)] @@ -6,6 +7,21 @@ pub enum Fork { Pacaya, Shasta, Permissionless, + Realtime, +} + +impl FromStr for Fork { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "pacaya" => Ok(Fork::Pacaya), + "shasta" => Ok(Fork::Shasta), + "permissionless" => Ok(Fork::Permissionless), + "realtime" => Ok(Fork::Realtime), + _ => Err(anyhow::anyhow!("Unknown fork: {}", s)), + } + } } impl Fork { diff --git a/common/src/fork_info/mod.rs b/common/src/fork_info/mod.rs index 2f17e436..58d886af 100644 --- a/common/src/fork_info/mod.rs +++ b/common/src/fork_info/mod.rs @@ -3,8 +3,10 @@ pub mod fork; use anyhow::Error; use config::ForkInfoConfig; pub use fork::Fork; +use std::str::FromStr; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use strum::IntoEnumIterator; +use tracing::info; #[derive(Debug, Clone)] pub struct ForkInfo { @@ -23,6 +25,13 @@ impl Default for ForkInfo { impl ForkInfo { pub fn from_config(config: ForkInfoConfig) -> Result { + // FORK env var overrides timestamp-based fork detection + if let Ok(fork_override) = std::env::var("FORK") { + let fork = Fork::from_str(&fork_override)?; + info!("FORK env var set, overriding fork detection to: {}", fork); + return Ok(Self { fork, config }); + } + let current_timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?; let fork = Self::choose_current_fork(&config, current_timestamp.as_secs())?; Ok(Self { fork, config }) diff --git a/common/src/l2/taiko_driver/mod.rs b/common/src/l2/taiko_driver/mod.rs index 5111a72f..06b9ac05 100644 --- a/common/src/l2/taiko_driver/mod.rs +++ b/common/src/l2/taiko_driver/mod.rs @@ -6,7 +6,10 @@ mod status_provider_trait; use crate::{metrics::Metrics, utils::rpc_client::HttpRPCClient}; use anyhow::Error; pub use config::TaikoDriverConfig; -use models::{BuildPreconfBlockRequestBody, BuildPreconfBlockResponse, TaikoStatus}; +use models::{ + BuildPreconfBlockRequestBody, BuildPreconfBlockResponse, ReorgStaleBlockRequest, + ReorgStaleBlockResponse, TaikoStatus, +}; pub use operation_type::OperationType; use serde_json::Value; pub use status_provider_trait::StatusProvider; @@ -73,6 +76,30 @@ impl TaikoDriver { } } + pub async fn reorg_stale_block( + &self, + new_head_block_number: u64, + ) -> Result { + const API_ENDPOINT: &str = "reorgStaleBlock"; + + let request_body = ReorgStaleBlockRequest { + new_head_block_number, + }; + + let response = self + .call_driver( + &self.preconf_rpc, + http::Method::POST, + API_ENDPOINT, + &request_body, + OperationType::ReorgStaleBlock, + ) + .await?; + + let reorg_response: ReorgStaleBlockResponse = serde_json::from_value(response)?; + Ok(reorg_response) + } + async fn call_driver( &self, client: &HttpRPCClient, diff --git a/common/src/l2/taiko_driver/models.rs b/common/src/l2/taiko_driver/models.rs index 6c9c2597..2d04a321 100644 --- a/common/src/l2/taiko_driver/models.rs +++ b/common/src/l2/taiko_driver/models.rs @@ -14,6 +14,7 @@ pub struct BuildPreconfBlockRequestBody { pub struct BuildPreconfBlockResponse { pub number: u64, pub hash: B256, + pub state_root: B256, pub parent_hash: B256, pub is_forced_inclusion: bool, } @@ -29,6 +30,7 @@ impl BuildPreconfBlockResponse { ) .ok()?, hash: Self::to_b256(header.get("hash")?.as_str()?)?, + state_root: Self::to_b256(header.get("stateRoot")?.as_str()?)?, parent_hash: Self::to_b256(header.get("parentHash")?.as_str()?)?, is_forced_inclusion, }) @@ -67,6 +69,19 @@ pub struct TaikoStatus { pub end_of_sequencing_block_hash: B256, } +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ReorgStaleBlockRequest { + pub new_head_block_number: u64, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ReorgStaleBlockResponse { + pub new_head_block_hash: B256, + pub blocks_removed: u64, +} + fn deserialize_end_of_sequencing_block_hash<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, diff --git a/common/src/l2/taiko_driver/operation_type.rs b/common/src/l2/taiko_driver/operation_type.rs index be2677af..83ce1bda 100644 --- a/common/src/l2/taiko_driver/operation_type.rs +++ b/common/src/l2/taiko_driver/operation_type.rs @@ -4,6 +4,7 @@ use std::fmt; pub enum OperationType { Preconfirm, Reanchor, + ReorgStaleBlock, Status, } @@ -12,6 +13,7 @@ impl fmt::Display for OperationType { let s = match self { OperationType::Preconfirm => "Preconfirm", OperationType::Reanchor => "Reanchor", + OperationType::ReorgStaleBlock => "ReorgStaleBlock", OperationType::Status => "Status", }; write!(f, "{s}") diff --git a/common/src/shared/transaction_monitor.rs b/common/src/shared/transaction_monitor.rs index fd3c7aae..d9a09e18 100644 --- a/common/src/shared/transaction_monitor.rs +++ b/common/src/shared/transaction_monitor.rs @@ -54,6 +54,9 @@ pub struct TransactionMonitorThread { metrics: Arc, chain_id: u64, sent_tx_hashes: Vec>, + tx_hash_notifier: Option>, + /// Notifies the caller whether the transaction was confirmed (true) or failed (false). + tx_result_notifier: Option>, } //#[derive(Debug)] @@ -103,6 +106,8 @@ impl TransactionMonitor { &self, tx: TransactionRequest, nonce: u64, + tx_hash_notifier: Option>, + tx_result_notifier: Option>, ) -> Result<(), Error> { let mut guard = self.join_handle.lock().await; if let Some(join_handle) = guard.as_ref() @@ -113,7 +118,7 @@ impl TransactionMonitor { )); } - let monitor_thread = TransactionMonitorThread::new( + let mut monitor_thread = TransactionMonitorThread::new( self.provider.clone(), self.config.clone(), nonce, @@ -121,6 +126,8 @@ impl TransactionMonitor { self.metrics.clone(), self.chain_id, ); + monitor_thread.tx_hash_notifier = tx_hash_notifier; + monitor_thread.tx_result_notifier = tx_result_notifier; let join_handle = monitor_thread.spawn_monitoring_task(tx); *guard = Some(join_handle); Ok(()) @@ -182,6 +189,8 @@ impl TransactionMonitorThread { metrics, chain_id, sent_tx_hashes: Vec::new(), + tx_hash_notifier: None, + tx_result_notifier: None, } } pub fn spawn_monitoring_task(mut self, tx: TransactionRequest) -> JoinHandle<()> { @@ -190,6 +199,12 @@ impl TransactionMonitorThread { }) } + fn notify_result(&mut self, success: bool) { + if let Some(notifier) = self.tx_result_notifier.take() { + let _ = notifier.send(success); + } + } + pub fn spawn_monitoring_task_with_builder( mut self, tx_builder: impl TransactionRequestBuilder, @@ -212,6 +227,7 @@ impl TransactionMonitorThread { if !matches!(tx.buildable_type(), Some(TxType::Eip1559 | TxType::Eip4844)) { self.send_error_signal(TransactionError::UnsupportedTransactionType) .await; + self.notify_result(false); return; } tx.set_chain_id(self.chain_id); @@ -265,11 +281,13 @@ impl TransactionMonitorThread { error!("Failed to get L1 block number: {}", e); self.send_error_signal(TransactionError::GetBlockNumberFailed) .await; + self.notify_result(false); return; } }; if sending_attempt > 0 && self.verify_tx_included(sending_attempt).await { + self.notify_result(true); return; } @@ -277,12 +295,18 @@ impl TransactionMonitorThread { if let Some(pending_tx) = self.send_transaction(tx_clone, sending_attempt).await { pending_tx } else { + self.notify_result(false); return; }; let tx_hash = *pending_tx.tx_hash(); self.sent_tx_hashes.push(tx_hash); + // Notify the first tx hash to the caller if requested + if let Some(notifier) = self.tx_hash_notifier.take() { + let _ = notifier.send(tx_hash); + } + if root_provider.is_none() { root_provider = Some(pending_tx.provider().clone()); } @@ -303,7 +327,7 @@ impl TransactionMonitorThread { max_fee_per_blob_gas ); - if self + if let Some(confirmed) = self .is_transaction_handled_by_builder( pending_tx.provider().clone(), tx_hash, @@ -312,6 +336,7 @@ impl TransactionMonitorThread { ) .await { + self.notify_result(confirmed); return; } @@ -326,14 +351,15 @@ impl TransactionMonitorThread { //Wait for transaction result let mut wait_attempt = 0; + let mut resolved = false; if let Some(root_provider) = root_provider { // We can use unwrap since tx_hashes is updated before root_provider let tx_hash = self .sent_tx_hashes .last() .expect("assert: tx_hashes is updated before root_provider"); - while wait_attempt < self.config.max_attempts_to_wait_tx - && !self + while wait_attempt < self.config.max_attempts_to_wait_tx { + if let Some(confirmed) = self .is_transaction_handled_by_builder( root_provider.clone(), *tx_hash, @@ -341,51 +367,63 @@ impl TransactionMonitorThread { self.config.max_attempts_to_send_tx, ) .await - && !self + { + self.notify_result(confirmed); + resolved = true; + break; + } + if self .verify_tx_included(wait_attempt + self.config.max_attempts_to_send_tx) .await - { + { + self.notify_result(true); + resolved = true; + break; + } warn!("🟣 Transaction watcher timed out without a result. Waiting..."); wait_attempt += 1; } } - if wait_attempt >= self.config.max_attempts_to_wait_tx { - error!( - "⛔ Transaction {} with nonce {} not confirmed", - if let Some(tx_hash) = self.sent_tx_hashes.last() { - tx_hash.to_string() - } else { - "unknown".to_string() - }, - self.nonce, - ); + if !resolved { + if wait_attempt >= self.config.max_attempts_to_wait_tx { + error!( + "⛔ Transaction {} with nonce {} not confirmed", + if let Some(tx_hash) = self.sent_tx_hashes.last() { + tx_hash.to_string() + } else { + "unknown".to_string() + }, + self.nonce, + ); - self.send_error_signal(TransactionError::NotConfirmed).await; + self.send_error_signal(TransactionError::NotConfirmed).await; + } + self.notify_result(false); } } - /// Returns true if transaction removed from mempool for any reason + /// Returns Some(true) if confirmed, Some(false) if failed, None if still pending. async fn is_transaction_handled_by_builder( &self, root_provider: RootProvider, tx_hash: B256, l1_block_at_send: u64, sending_attempt: u64, - ) -> bool { + ) -> Option { loop { let check_tx = PendingTransactionBuilder::new(root_provider.clone(), tx_hash); let tx_status = self.wait_for_tx_receipt(check_tx, sending_attempt).await; match tx_status { - TxStatus::Confirmed => return true, + TxStatus::Confirmed => return Some(true), TxStatus::Failed(err_str) => { if let Some(error) = tools::convert_error_payload(&err_str) { self.send_error_signal(error).await; - return true; + return Some(false); } self.send_error_signal(TransactionError::TransactionReverted) .await; - return true; + return Some(false); } TxStatus::Pending => {} // Continue with retry attempts } @@ -397,7 +435,7 @@ impl TransactionMonitorThread { error!("Failed to get L1 block number: {}", e); self.send_error_signal(TransactionError::GetBlockNumberFailed) .await; - return true; + return Some(false); } }; if current_l1_height != l1_block_at_send { @@ -409,7 +447,7 @@ impl TransactionMonitorThread { ); } - false + None } async fn send_transaction( diff --git a/common/src/utils/logging.rs b/common/src/utils/logging.rs index a52fe711..23c0e25a 100644 --- a/common/src/utils/logging.rs +++ b/common/src/utils/logging.rs @@ -13,6 +13,11 @@ pub fn init_logging() { .parse() .expect("assert: can parse env filter directive"), ) + .add_directive( + "h2=info" + .parse() + .expect("assert: can parse env filter directive"), + ) .add_directive( "alloy_transport=info" .parse() diff --git a/deny.toml b/deny.toml index 881ead72..172b18c1 100644 --- a/deny.toml +++ b/deny.toml @@ -19,7 +19,8 @@ ignore = [ "RUSTSEC-2024-0436", # paste - no longer maintained "RUSTSEC-2023-0071", # https://github.com/NethermindEth/Catalyst/issues/735 "RUSTSEC-2025-0141", # unmaintained advisory detected, used by alethia-reth - "RUSTSEC-2026-0002", # lru IterMut soundness; transitive (discv5/kona), upgrade when deps allow + "RUSTSEC-2024-0384", # instant unmaintained; transitive via sled (used by realtime/shasta bridge handler) + "RUSTSEC-2025-0057", # fxhash unmaintained; transitive via sled (used by realtime/shasta bridge handler) ] [licenses] diff --git a/node/Cargo.toml b/node/Cargo.toml index f6fc688d..bfb801ca 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -23,6 +23,7 @@ clap = { workspace = true } common = { workspace = true } pacaya = { workspace = true } permissionless = { workspace = true } +realtime = { workspace = true } rustls = { workspace = true } serde_json = { workspace = true } shasta = { workspace = true } diff --git a/node/src/main.rs b/node/src/main.rs index 009f8dbb..671c86d1 100644 --- a/node/src/main.rs +++ b/node/src/main.rs @@ -5,6 +5,7 @@ use common::{ utils::cancellation_token::CancellationToken, }; use pacaya::create_pacaya_node; +use realtime::create_realtime_node; use std::sync::Arc; use tokio::signal::unix::{SignalKind, signal}; use tracing::{error, info}; @@ -83,10 +84,9 @@ async fn run_node(iteration: u64, metrics: Arc) -> Result { - // TODO pacaya::utils::config::Config let next_fork_timestamp = fork_info.config.fork_switch_timestamps.get(1); info!( - "Current fork: PACAYA 🌋, next fork timestamp: {:?}", + "Current fork: PACAYA, next fork timestamp: {:?}", next_fork_timestamp ); create_pacaya_node( @@ -98,7 +98,7 @@ async fn run_node(iteration: u64, metrics: Arc) -> Result { - info!("Current fork: SHASTA 🌋"); + info!("Current fork: SHASTA"); shasta::create_shasta_node( config.clone(), metrics.clone(), @@ -108,7 +108,7 @@ async fn run_node(iteration: u64, metrics: Arc) -> Result { - info!("Current fork: PERMISSIONLESS 🌋"); + info!("Current fork: PERMISSIONLESS"); permissionless::create_permissionless_node( config.clone(), metrics.clone(), @@ -117,6 +117,16 @@ async fn run_node(iteration: u64, metrics: Arc) -> Result { + info!("Current fork: REALTIME"); + create_realtime_node( + config.clone(), + metrics.clone(), + cancel_token.clone(), + fork_info, + ) + .await?; + } } metrics::server::serve_metrics(metrics.clone(), cancel_token.clone()); diff --git a/pacaya/src/l1/execution_layer.rs b/pacaya/src/l1/execution_layer.rs index b2a2c8dd..2fda9cff 100644 --- a/pacaya/src/l1/execution_layer.rs +++ b/pacaya/src/l1/execution_layer.rs @@ -253,7 +253,7 @@ impl ExecutionLayer { .context("get_preconfer_nonce_pending (send_batch_to_l1)")?; // Spawn a monitor for this transaction self.transaction_monitor - .monitor_new_transaction(tx, pending_nonce) + .monitor_new_transaction(tx, pending_nonce, None, None) .await .map_err(|e| Error::msg(format!("Sending batch to L1 failed: {e}")))?; diff --git a/permissionless/src/lib.rs b/permissionless/src/lib.rs index c6b07c5a..31625ad8 100644 --- a/permissionless/src/lib.rs +++ b/permissionless/src/lib.rs @@ -7,6 +7,7 @@ mod utils; use crate::node::block_advancer::PermissionlessBlockAdvancer; use crate::node::config::NodeConfig; use crate::utils::config::Config as PermissionlessConfig; +use alloy::primitives::Address; use anyhow::Error; use common::{ batch_builder::BatchBuilderConfig, @@ -45,6 +46,8 @@ pub async fn create_permissionless_node( let (transaction_error_sender, transaction_error_receiver) = mpsc::channel(100); let shasta_l1_config = ShastaEthereumL1Config { shasta_inbox: permissionless_config.shasta_inbox, + proposer_multicall: Address::ZERO, + bridge: Address::ZERO, }; let ethereum_l1 = Arc::new( EthereumL1::::new( diff --git a/permissionless/src/node/block_advancer.rs b/permissionless/src/node/block_advancer.rs index a0aeec3a..b5c1472f 100644 --- a/permissionless/src/node/block_advancer.rs +++ b/permissionless/src/node/block_advancer.rs @@ -4,11 +4,11 @@ use anyhow::Error; use common::l2::taiko_driver::{OperationType, models::BuildPreconfBlockResponse}; use common::shared::l2_slot_info_v2::L2SlotContext; use secp256k1::SecretKey; +use shasta::l2::bindings::ICheckpointStore::Checkpoint; use shasta::{BlockAdvancer, L2BlockV2Payload, l2::taiko::Taiko}; use std::future::Future; use std::pin::Pin; use std::sync::Arc; -use taiko_bindings::anchor::ICheckpointStore::Checkpoint; use tracing::info; pub struct PermissionlessBlockAdvancer { @@ -51,7 +51,7 @@ impl BlockAdvancer for PermissionlessBlockAdvancer { let anchor_tx = self .taiko .l2_execution_layer() - .construct_anchor_tx(&l2_slot_context.info, anchor_block_params) + .construct_anchor_tx(&l2_slot_context.info, (anchor_block_params, vec![])) .await .map_err(|e| anyhow::anyhow!("Failed to construct anchor tx: {}", e))?; @@ -79,6 +79,7 @@ impl BlockAdvancer for PermissionlessBlockAdvancer { Ok(BuildPreconfBlockResponse { number: l2_slot_context.info.parent_id() + 1, hash: B256::ZERO, // TODO: missing hash from the response, do we need it for permissionless? + state_root: B256::ZERO, parent_hash: *l2_slot_context.info.parent_hash(), is_forced_inclusion: l2_block_payload.is_forced_inclusion, }) diff --git a/realtime/Cargo.toml b/realtime/Cargo.toml new file mode 100644 index 00000000..a9a9d7ed --- /dev/null +++ b/realtime/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "realtime" +version.workspace = true +edition.workspace = true +repository.workspace = true +license.workspace = true +publish = false + +[dependencies] +alloy = { workspace = true } +alloy-json-rpc = { workspace = true } +alloy-rlp = { workspace = true } +anyhow = { workspace = true } +async-trait = { workspace = true } +chrono = { workspace = true } +common = { workspace = true } +dotenvy = { workspace = true } +flate2 = { workspace = true } +futures-util = { workspace = true } +hex = { workspace = true } +http = { workspace = true } +jsonrpsee = { workspace = true } +jsonwebtoken = { workspace = true } +pacaya = { workspace = true } +prometheus = { workspace = true } +reqwest = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +sled = { workspace = true } +taiko_alethia_reth = { workspace = true } +taiko_bindings = { workspace = true } +taiko_protocol = { workspace = true } +taiko_rpc = { workspace = true } +tokio = { workspace = true } +tokio-util = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +warp = { workspace = true } + +[dev-dependencies] +mockito = { workspace = true } +tokio = { workspace = true, features = ["full", "test-util"] } + +[lints] +workspace = true diff --git a/realtime/src/chain_monitor/mod.rs b/realtime/src/chain_monitor/mod.rs new file mode 100644 index 00000000..2af8d789 --- /dev/null +++ b/realtime/src/chain_monitor/mod.rs @@ -0,0 +1,12 @@ +use crate::l1::bindings::RealTimeInbox; +use common::chain_monitor::ChainMonitor; +use tracing::info; + +pub type RealtimeChainMonitor = ChainMonitor; + +pub fn print_proposed_and_proved_info(event: &RealTimeInbox::ProposedAndProved) { + info!( + "ProposedAndProved event → proposalHash = {}, lastFinalizedBlockHash = {}, maxAnchorBlockNumber = {}", + event.proposalHash, event.lastFinalizedBlockHash, event.maxAnchorBlockNumber + ); +} diff --git a/realtime/src/l1/abi/Multicall.json b/realtime/src/l1/abi/Multicall.json new file mode 100644 index 00000000..53b423ce --- /dev/null +++ b/realtime/src/l1/abi/Multicall.json @@ -0,0 +1,44 @@ +{ + "abi": [ + { + "type": "receive", + "stateMutability": "payable" + }, + { + "type": "function", + "name": "multicall", + "inputs": [ + { + "name": "calls", + "type": "tuple[]", + "internalType": "struct Multicall.Call[]", + "components": [ + { + "name": "target", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [ + { + "name": "results", + "type": "bytes[]", + "internalType": "bytes[]" + } + ], + "stateMutability": "payable" + } + ] +} \ No newline at end of file diff --git a/realtime/src/l1/abi/RealTimeInbox.json b/realtime/src/l1/abi/RealTimeInbox.json new file mode 100644 index 00000000..18dfc773 --- /dev/null +++ b/realtime/src/l1/abi/RealTimeInbox.json @@ -0,0 +1 @@ +{"abi":[{"type":"function","name":"activate","inputs":[{"name":"_genesisBlockHash","type":"bytes32","internalType":"bytes32"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"getConfig","inputs":[],"outputs":[{"name":"config_","type":"tuple","internalType":"struct IRealTimeInbox.Config","components":[{"name":"proofVerifier","type":"address","internalType":"address"},{"name":"signalService","type":"address","internalType":"address"},{"name":"basefeeSharingPctg","type":"uint8","internalType":"uint8"}]}],"stateMutability":"view"},{"type":"function","name":"getLastFinalizedBlockHash","inputs":[],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"propose","inputs":[{"name":"_data","type":"bytes","internalType":"bytes"},{"name":"_checkpoint","type":"tuple","internalType":"struct ICheckpointStore.Checkpoint","components":[{"name":"blockNumber","type":"uint48","internalType":"uint48"},{"name":"blockHash","type":"bytes32","internalType":"bytes32"},{"name":"stateRoot","type":"bytes32","internalType":"bytes32"}]},{"name":"_proof","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"Activated","inputs":[{"name":"genesisBlockHash","type":"bytes32","indexed":false,"internalType":"bytes32"}],"anonymous":false},{"type":"event","name":"ProposedAndProved","inputs":[{"name":"proposalHash","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"lastFinalizedBlockHash","type":"bytes32","indexed":false,"internalType":"bytes32"},{"name":"maxAnchorBlockNumber","type":"uint48","indexed":false,"internalType":"uint48"},{"name":"basefeeSharingPctg","type":"uint8","indexed":false,"internalType":"uint8"},{"name":"sources","type":"tuple[]","indexed":false,"internalType":"struct IInbox.DerivationSource[]","components":[{"name":"isForcedInclusion","type":"bool","internalType":"bool"},{"name":"blobSlice","type":"tuple","internalType":"struct LibBlobs.BlobSlice","components":[{"name":"blobHashes","type":"bytes32[]","internalType":"bytes32[]"},{"name":"offset","type":"uint24","internalType":"uint24"},{"name":"timestamp","type":"uint48","internalType":"uint48"}]}]},{"name":"signalSlots","type":"bytes32[]","indexed":false,"internalType":"bytes32[]"},{"name":"checkpoint","type":"tuple","indexed":false,"internalType":"struct ICheckpointStore.Checkpoint","components":[{"name":"blockNumber","type":"uint48","internalType":"uint48"},{"name":"blockHash","type":"bytes32","internalType":"bytes32"},{"name":"stateRoot","type":"bytes32","internalType":"bytes32"}]}],"anonymous":false}]} \ No newline at end of file diff --git a/realtime/src/l1/bindings.rs b/realtime/src/l1/bindings.rs new file mode 100644 index 00000000..d07df623 --- /dev/null +++ b/realtime/src/l1/bindings.rs @@ -0,0 +1,92 @@ +#![allow(clippy::too_many_arguments)] + +use alloy::sol; + +sol!( + #[allow(missing_docs)] + #[sol(rpc)] + #[derive(Debug, Default)] + RealTimeInbox, + "src/l1/abi/RealTimeInbox.json" +); + +sol!( + #[allow(missing_docs)] + #[sol(rpc)] + #[derive(Debug)] + Multicall, + "src/l1/abi/Multicall.json" +); + +// Define ProposeInput and BlobReference manually since the RealTimeInbox ABI +// only exposes propose(bytes _data, ...) where _data is abi.encode(ProposeInput). +// These types are internal to the contract but needed for encoding. +sol! { + struct BlobReference { + uint16 blobStartIndex; + uint16 numBlobs; + uint24 offset; + } + + struct ProposeInput { + BlobReference blobReference; + bytes32[] signalSlots; + uint48 maxAnchorBlockNumber; + } + + // SurgeVerifier SubProof encoding + struct SubProof { + uint8 proofBitFlag; + bytes data; + } +} + +/// Proof types supported by the SurgeVerifier. +/// Each variant maps to a bit flag used in `SubProof.proofBitFlag`. +#[derive(Debug, Clone, Copy)] +pub enum ProofType { + Risc0, // 0b00000001 + Sp1, // 0b00000010 + Zisk, // 0b00000100 +} + +impl ProofType { + pub fn proof_bit_flag(&self) -> u8 { + match self { + ProofType::Risc0 => 1, + ProofType::Sp1 => 1 << 1, + ProofType::Zisk => 1 << 2, + } + } + + /// Returns the proof type string expected by Raiko. + pub fn raiko_proof_type(&self) -> &'static str { + match self { + ProofType::Risc0 => "risc0", + ProofType::Sp1 => "sp1", + ProofType::Zisk => "zisk", + } + } +} + +impl std::str::FromStr for ProofType { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "risc0" => Ok(ProofType::Risc0), + "sp1" => Ok(ProofType::Sp1), + "zisk" => Ok(ProofType::Zisk), + _ => Err(anyhow::anyhow!( + "Invalid PROOF_TYPE '{}'. Must be one of: sp1, risc0, zisk", + s + )), + } + } +} + +impl std::fmt::Display for ProofType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.raiko_proof_type()) + } +} diff --git a/realtime/src/l1/config.rs b/realtime/src/l1/config.rs new file mode 100644 index 00000000..d4634cb1 --- /dev/null +++ b/realtime/src/l1/config.rs @@ -0,0 +1,34 @@ +use crate::l1::bindings::ProofType; +use crate::raiko::RaikoClient; +use crate::utils::config::RealtimeConfig; +use alloy::primitives::Address; + +#[derive(Clone)] +pub struct ContractAddresses { + pub realtime_inbox: Address, + pub proposer_multicall: Address, + pub bridge: Address, +} + +pub struct EthereumL1Config { + pub realtime_inbox: Address, + pub proposer_multicall: Address, + pub bridge: Address, + pub proof_type: ProofType, + pub raiko_client: RaikoClient, +} + +impl TryFrom for EthereumL1Config { + type Error = anyhow::Error; + + fn try_from(config: RealtimeConfig) -> Result { + let raiko_client = RaikoClient::new(&config); + Ok(EthereumL1Config { + realtime_inbox: config.realtime_inbox, + proposer_multicall: config.proposer_multicall, + bridge: config.bridge, + proof_type: config.proof_type, + raiko_client, + }) + } +} diff --git a/realtime/src/l1/execution_layer.rs b/realtime/src/l1/execution_layer.rs new file mode 100644 index 00000000..50d906d2 --- /dev/null +++ b/realtime/src/l1/execution_layer.rs @@ -0,0 +1,351 @@ +use super::config::EthereumL1Config; +use super::proposal_tx_builder::ProposalTxBuilder; +use super::protocol_config::ProtocolConfig; +use crate::l1::bindings::RealTimeInbox::{self, RealTimeInboxInstance}; +use crate::node::proposal_manager::proposal::Proposal; +use crate::raiko::RaikoClient; +use crate::shared_abi::bindings::{ + Bridge::MessageSent, IBridge::Message, SignalService::SignalSent, +}; +use crate::{l1::config::ContractAddresses, node::proposal_manager::bridge_handler::UserOp}; +use alloy::{ + eips::{BlockId, BlockNumberOrTag}, + primitives::{Address, B256, FixedBytes}, + providers::{DynProvider, ext::DebugApi}, + rpc::types::{ + TransactionRequest, + trace::geth::{ + GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingCallOptions, + GethDebugTracingOptions, + }, + }, + sol_types::SolEvent, +}; +use anyhow::{Error, anyhow}; +use common::{ + l1::{ + traits::{ELTrait, PreconferProvider}, + transaction_error::TransactionError, + }, + metrics::Metrics, + shared::{ + alloy_tools, execution_layer::ExecutionLayer as ExecutionLayerCommon, + transaction_monitor::TransactionMonitor, + }, +}; +use pacaya::l1::{operators_cache::OperatorError, traits::PreconfOperator}; +use std::sync::Arc; +use tokio::sync::mpsc::Sender; +use tracing::info; + +pub struct ExecutionLayer { + common: ExecutionLayerCommon, + provider: DynProvider, + preconfer_address: Address, + pub transaction_monitor: TransactionMonitor, + contract_addresses: ContractAddresses, + realtime_inbox: RealTimeInboxInstance, + #[allow(dead_code)] + raiko_client: RaikoClient, + proof_type: crate::l1::bindings::ProofType, +} + +impl ELTrait for ExecutionLayer { + type Config = EthereumL1Config; + async fn new( + common_config: common::l1::config::EthereumL1Config, + specific_config: Self::Config, + transaction_error_channel: Sender, + metrics: Arc, + ) -> Result { + let provider = alloy_tools::construct_alloy_provider( + &common_config.signer, + common_config + .execution_rpc_urls + .first() + .ok_or_else(|| anyhow!("L1 RPC URL is required"))?, + ) + .await?; + let common = + ExecutionLayerCommon::new(provider.clone(), common_config.signer.get_address()).await?; + + let transaction_monitor = TransactionMonitor::new( + provider.clone(), + &common_config, + transaction_error_channel, + metrics.clone(), + common.chain_id(), + ) + .await + .map_err(|e| Error::msg(format!("Failed to create TransactionMonitor: {e}")))?; + + let realtime_inbox = RealTimeInbox::new(specific_config.realtime_inbox, provider.clone()); + + let config = realtime_inbox + .getConfig() + .call() + .await + .map_err(|e| anyhow::anyhow!("Failed to call getConfig for RealTimeInbox: {e}"))?; + + tracing::info!( + "RealTimeInbox: {}, proofVerifier: {}, signalService: {}", + specific_config.realtime_inbox, + config.proofVerifier, + config.signalService, + ); + + let contract_addresses = ContractAddresses { + realtime_inbox: specific_config.realtime_inbox, + proposer_multicall: specific_config.proposer_multicall, + bridge: specific_config.bridge, + }; + + let proof_type = specific_config.proof_type; + let raiko_client = specific_config.raiko_client; + + Ok(Self { + common, + provider, + preconfer_address: common_config.signer.get_address(), + transaction_monitor, + contract_addresses, + realtime_inbox, + raiko_client, + proof_type, + }) + } + + fn common(&self) -> &ExecutionLayerCommon { + &self.common + } +} + +impl PreconferProvider for ExecutionLayer { + async fn get_preconfer_wallet_eth(&self) -> Result { + self.common() + .get_account_balance(self.preconfer_address) + .await + } + + async fn get_preconfer_nonce_pending(&self) -> Result { + self.common() + .get_account_nonce(self.preconfer_address, BlockNumberOrTag::Pending) + .await + } + + async fn get_preconfer_nonce_latest(&self) -> Result { + self.common() + .get_account_nonce(self.preconfer_address, BlockNumberOrTag::Latest) + .await + } + + fn get_preconfer_address(&self) -> Address { + self.preconfer_address + } +} + +impl PreconfOperator for ExecutionLayer { + fn get_preconfer_address(&self) -> Address { + self.preconfer_address + } + + async fn get_operators_for_current_and_next_epoch( + &self, + _current_epoch_timestamp: u64, + _current_slot_timestamp: u64, + ) -> Result<(Address, Address), OperatorError> { + // RealTime: anyone can propose, but we still use operator tracking for slot management. + // Return self as both current and next operator. + Ok((self.preconfer_address, self.preconfer_address)) + } + + async fn is_preconf_router_specified_in_taiko_wrapper(&self) -> Result { + Ok(true) + } + + async fn get_l2_height_from_taiko_inbox(&self) -> Result { + Ok(0) + } + + async fn get_handover_window_slots(&self) -> Result { + Err(anyhow::anyhow!( + "Not implemented for RealTime execution layer" + )) + } +} + +impl ExecutionLayer { + #[allow(dead_code)] + pub fn get_raiko_client(&self) -> &RaikoClient { + &self.raiko_client + } + + pub async fn send_batch_to_l1( + &self, + batch: Proposal, + tx_hash_notifier: Option>, + tx_result_notifier: Option>, + ) -> Result<(), Error> { + info!( + "📦 Proposing with {} blocks | user_ops: {:?} | signal_slots: {:?} | l1_calls: {:?} | zk_proof: {}", + batch.l2_blocks.len(), + batch.user_ops, + batch.signal_slots, + batch.l1_calls, + batch.zk_proof.is_some(), + ); + + let builder = ProposalTxBuilder::new(self.provider.clone(), 10, self.proof_type); + + let tx = builder + .build_propose_tx( + batch, + self.preconfer_address, + self.contract_addresses.clone(), + ) + .await?; + + let pending_nonce = self.get_preconfer_nonce_pending().await?; + self.transaction_monitor + .monitor_new_transaction(tx, pending_nonce, tx_hash_notifier, tx_result_notifier) + .await + .map_err(|e| Error::msg(format!("Sending batch to L1 failed: {e}")))?; + + Ok(()) + } + + pub async fn is_transaction_in_progress(&self) -> Result { + self.transaction_monitor.is_transaction_in_progress().await + } + + pub async fn fetch_protocol_config(&self) -> Result { + let config = self + .realtime_inbox + .getConfig() + .call() + .await + .map_err(|e| anyhow::anyhow!("Failed to call getConfig for RealTimeInbox: {e}"))?; + + info!( + "RealTimeInbox config: basefeeSharingPctg: {}", + config.basefeeSharingPctg, + ); + + Ok(ProtocolConfig::from(&config)) + } + + pub async fn get_last_finalized_block_hash(&self) -> Result { + let result = self + .realtime_inbox + .getLastFinalizedBlockHash() + .call() + .await + .map_err(|e| anyhow::anyhow!("Failed to call getLastFinalizedBlockHash: {e}"))?; + + Ok(result) + } +} + +// Surge: L1 EL ops for Bridge Handler + +use alloy::rpc::types::trace::geth::{CallFrame, CallLogFrame}; + +fn collect_logs_recursive(frame: &CallFrame) -> Vec { + let mut logs = frame.logs.clone(); + + for subcall in &frame.calls { + logs.extend(collect_logs_recursive(subcall)); + } + + logs +} + +pub trait L1BridgeHandlerOps { + async fn find_message_and_signal_slot( + &self, + user_op: UserOp, + ) -> Result)>, anyhow::Error>; +} + +impl L1BridgeHandlerOps for ExecutionLayer { + async fn find_message_and_signal_slot( + &self, + user_op_data: UserOp, + ) -> Result)>, anyhow::Error> { + let tx_request = TransactionRequest::default() + .from(self.preconfer_address) + .to(user_op_data.submitter) + .input(user_op_data.calldata.into()); + + let mut tracer_config = serde_json::Map::new(); + tracer_config.insert("withLog".to_string(), serde_json::Value::Bool(true)); + tracer_config.insert("onlyTopCall".to_string(), serde_json::Value::Bool(false)); + + let tracing_options = GethDebugTracingOptions { + tracer: Some(GethDebugTracerType::BuiltInTracer( + GethDebugBuiltInTracerType::CallTracer, + )), + tracer_config: serde_json::Value::Object(tracer_config).into(), + ..Default::default() + }; + + let call_options = GethDebugTracingCallOptions { + tracing_options, + ..Default::default() + }; + + let trace_result = self + .provider + .debug_trace_call( + tx_request, + BlockId::Number(BlockNumberOrTag::Latest), + call_options, + ) + .await + .map_err(|e| anyhow!("Failed to simulate executeBatch on L1: {e}"))?; + + tracing::debug!("Received trace result: {:?}", trace_result); + + let mut message: Option = None; + let mut slot: Option> = None; + + if let alloy::rpc::types::trace::geth::GethTrace::CallTracer(call_frame) = trace_result { + let all_logs = collect_logs_recursive(&call_frame); + tracing::debug!("Collected {} logs from call trace", all_logs.len()); + + for log in all_logs { + if let Some(topics) = &log.topics + && !topics.is_empty() + { + if topics[0] == MessageSent::SIGNATURE_HASH { + let log_data = alloy::primitives::LogData::new_unchecked( + topics.clone(), + log.data.clone().unwrap_or_default(), + ); + let decoded = MessageSent::decode_log_data(&log_data) + .map_err(|e| anyhow!("Failed to decode MessageSent event L1: {e}"))?; + + message = Some(decoded.message); + } else if topics[0] == SignalSent::SIGNATURE_HASH { + let log_data = alloy::primitives::LogData::new_unchecked( + topics.clone(), + log.data.clone().unwrap_or_default(), + ); + let decoded = SignalSent::decode_log_data(&log_data) + .map_err(|e| anyhow!("Failed to decode SignalSent event L1: {e}"))?; + + slot = Some(decoded.slot); + } + } + } + } + + tracing::debug!("{:?} {:?}", message, slot); + + if let (Some(message), Some(slot)) = (message, slot) { + return Ok(Some((message, slot))); + } + + Ok(None) + } +} diff --git a/realtime/src/l1/mod.rs b/realtime/src/l1/mod.rs new file mode 100644 index 00000000..7bcc9c57 --- /dev/null +++ b/realtime/src/l1/mod.rs @@ -0,0 +1,5 @@ +pub mod bindings; +pub mod config; +pub mod execution_layer; +pub mod proposal_tx_builder; +pub mod protocol_config; diff --git a/realtime/src/l1/proposal_tx_builder.rs b/realtime/src/l1/proposal_tx_builder.rs new file mode 100644 index 00000000..0e713525 --- /dev/null +++ b/realtime/src/l1/proposal_tx_builder.rs @@ -0,0 +1,233 @@ +use crate::l1::{ + bindings::{BlobReference, Multicall, ProofType, ProposeInput, RealTimeInbox, SubProof}, + config::ContractAddresses, +}; +use crate::node::proposal_manager::{ + bridge_handler::{L1Call, UserOp}, + proposal::Proposal, +}; +use crate::shared_abi::bindings::Bridge; +use alloy::{ + consensus::SidecarBuilder, + eips::eip4844::BlobTransactionSidecar, + network::TransactionBuilder4844, + primitives::{ + Address, Bytes, U256, + aliases::{U24, U48}, + }, + providers::{DynProvider, Provider}, + rpc::types::TransactionRequest, + sol_types::SolValue, +}; +use anyhow::Error; +use common::l1::fees_per_gas::FeesPerGas; +use taiko_protocol::shasta::{ + BlobCoder, + manifest::{BlockManifest, DerivationSourceManifest}, +}; +use tracing::{info, warn}; + +pub struct ProposalTxBuilder { + provider: DynProvider, + extra_gas_percentage: u64, + proof_type: ProofType, +} + +impl ProposalTxBuilder { + pub fn new(provider: DynProvider, extra_gas_percentage: u64, proof_type: ProofType) -> Self { + Self { + provider, + extra_gas_percentage, + proof_type, + } + } + + #[allow(clippy::too_many_arguments)] + pub async fn build_propose_tx( + &self, + batch: Proposal, + from: Address, + contract_addresses: ContractAddresses, + ) -> Result { + let tx_blob = self + .build_propose_blob(batch, from, contract_addresses) + .await?; + let tx_blob_gas = match self.provider.estimate_gas(tx_blob.clone()).await { + Ok(gas) => gas, + Err(e) => { + warn!( + "Build proposeBatch: Failed to estimate gas for blob transaction: {}. Force-sending with 500000 gas.", + e + ); + 500_000 + } + }; + let tx_blob_gas = tx_blob_gas + tx_blob_gas * self.extra_gas_percentage / 100; + + let fees_per_gas = match FeesPerGas::get_fees_per_gas(&self.provider).await { + Ok(fees_per_gas) => fees_per_gas, + Err(e) => { + warn!("Build proposeBatch: Failed to get fees per gas: {}", e); + return Ok(tx_blob); + } + }; + + let tx_blob = fees_per_gas.update_eip4844(tx_blob, tx_blob_gas); + + Ok(tx_blob) + } + + #[allow(clippy::too_many_arguments)] + pub async fn build_propose_blob( + &self, + batch: Proposal, + from: Address, + contract_addresses: ContractAddresses, + ) -> Result { + let mut multicalls: Vec = vec![]; + + // Add all user ops to multicall + for user_op in &batch.user_ops { + let user_op_call = self.build_user_op_call(user_op.clone()); + info!("Added user op to Multicall: {:?}", &user_op_call); + multicalls.push(user_op_call); + } + + // Build the propose call and blob sidecar + let (propose_call, blob_sidecar) = self + .build_propose_call(&batch, contract_addresses.realtime_inbox) + .await?; + + // If no user ops or L1 calls, send directly to inbox (skip multicall) + if batch.user_ops.is_empty() && batch.l1_calls.is_empty() { + info!("Sending proposal directly to RealTimeInbox (no multicall)"); + let tx = TransactionRequest::default() + .to(contract_addresses.realtime_inbox) + .from(from) + .input(propose_call.data.into()) + .with_blob_sidecar(blob_sidecar); + return Ok(tx); + } + + info!("Added proposal to Multicall: {:?}", &propose_call); + multicalls.push(propose_call.clone()); + + // Add all L1 calls + for l1_call in &batch.l1_calls { + let l1_call_call = self.build_l1_call_call(l1_call.clone(), contract_addresses.bridge); + info!("Added L1 call to Multicall: {:?}", &l1_call_call); + multicalls.push(l1_call_call); + } + + let multicall = Multicall::new(contract_addresses.proposer_multicall, &self.provider); + let call = multicall.multicall(multicalls); + + let tx = TransactionRequest::default() + .to(contract_addresses.proposer_multicall) + .from(from) + .input(call.calldata().clone().into()) + .with_blob_sidecar(blob_sidecar); + + Ok(tx) + } + + fn build_user_op_call(&self, user_op_data: UserOp) -> Multicall::Call { + Multicall::Call { + target: user_op_data.submitter, + value: U256::ZERO, + data: user_op_data.calldata, + } + } + + async fn build_propose_call( + &self, + batch: &Proposal, + inbox_address: Address, + ) -> Result<(Multicall::Call, BlobTransactionSidecar), anyhow::Error> { + let mut block_manifests = >::with_capacity(batch.l2_blocks.len()); + for l2_block in &batch.l2_blocks { + block_manifests.push(BlockManifest { + timestamp: l2_block.timestamp_sec, + coinbase: l2_block.coinbase, + anchor_block_number: l2_block.anchor_block_number, + gas_limit: l2_block.gas_limit_without_anchor, + transactions: l2_block + .prebuilt_tx_list + .tx_list + .iter() + .map(|tx| tx.clone().into()) + .collect(), + }); + } + + let manifest = DerivationSourceManifest { + blocks: block_manifests, + }; + + let manifest_data = manifest + .encode_and_compress() + .map_err(|e| Error::msg(format!("Can't encode and compress manifest: {e}")))?; + + let sidecar_builder: SidecarBuilder = SidecarBuilder::from_slice(&manifest_data); + let sidecar: BlobTransactionSidecar = sidecar_builder.build()?; + + let inbox = RealTimeInbox::new(inbox_address, self.provider.clone()); + + // Encode the raw proof as SubProof[] for the SurgeVerifier + let raw_proof = batch + .zk_proof + .as_ref() + .ok_or_else(|| anyhow::anyhow!("ZK proof not set on proposal"))? + .clone(); + + let sub_proofs = vec![SubProof { + proofBitFlag: self.proof_type.proof_bit_flag(), + data: Bytes::from(raw_proof), + }]; + let proof = Bytes::from(sub_proofs.abi_encode()); + + // Build ProposeInput and ABI-encode it as the _data parameter + let blob_reference = BlobReference { + blobStartIndex: 0, + numBlobs: sidecar.blobs.len().try_into()?, + offset: U24::ZERO, + }; + + let propose_input = ProposeInput { + blobReference: blob_reference, + signalSlots: batch.signal_slots.clone(), + maxAnchorBlockNumber: U48::from(batch.max_anchor_block_number), + }; + + let encoded_input = Bytes::from(propose_input.abi_encode()); + + // Convert L1 Checkpoint type for the propose call + let checkpoint = crate::l1::bindings::ICheckpointStore::Checkpoint { + blockNumber: batch.checkpoint.blockNumber, + blockHash: batch.checkpoint.blockHash, + stateRoot: batch.checkpoint.stateRoot, + }; + + let call = inbox.propose(encoded_input, checkpoint, proof); + + Ok(( + Multicall::Call { + target: inbox_address, + value: U256::ZERO, + data: call.calldata().clone(), + }, + sidecar, + )) + } + + fn build_l1_call_call(&self, l1_call: L1Call, bridge_address: Address) -> Multicall::Call { + let bridge = Bridge::new(bridge_address, &self.provider); + let call = bridge.processMessage(l1_call.message_from_l2, l1_call.signal_slot_proof); + + Multicall::Call { + target: bridge_address, + value: U256::ZERO, + data: call.calldata().clone(), + } + } +} diff --git a/realtime/src/l1/protocol_config.rs b/realtime/src/l1/protocol_config.rs new file mode 100644 index 00000000..7b96307f --- /dev/null +++ b/realtime/src/l1/protocol_config.rs @@ -0,0 +1,33 @@ +use crate::l1::bindings::IRealTimeInbox::Config; +use alloy::primitives::Address; + +#[derive(Clone, Default)] +pub struct ProtocolConfig { + pub basefee_sharing_pctg: u8, + #[allow(dead_code)] + pub proof_verifier: Address, + #[allow(dead_code)] + pub signal_service: Address, +} + +impl From<&Config> for ProtocolConfig { + fn from(config: &Config) -> Self { + Self { + basefee_sharing_pctg: config.basefeeSharingPctg, + proof_verifier: config.proofVerifier, + signal_service: config.signalService, + } + } +} + +impl ProtocolConfig { + pub fn get_basefee_sharing_pctg(&self) -> u8 { + self.basefee_sharing_pctg + } + + /// Use the EVM blockhash() 256-block limit as the max anchor height offset. + #[allow(dead_code)] + pub fn get_max_anchor_height_offset(&self) -> u64 { + 256 + } +} diff --git a/realtime/src/l2/abi/Anchor.json b/realtime/src/l2/abi/Anchor.json new file mode 100644 index 00000000..b1ecd4b7 --- /dev/null +++ b/realtime/src/l2/abi/Anchor.json @@ -0,0 +1 @@ +{"abi":[{"type":"constructor","inputs":[{"name":"_checkpointStore","type":"address","internalType":"contract ICheckpointStore"},{"name":"_l1ChainId","type":"uint64","internalType":"uint64"}],"stateMutability":"nonpayable"},{"type":"function","name":"ANCHOR_GAS_LIMIT","inputs":[],"outputs":[{"name":"","type":"uint64","internalType":"uint64"}],"stateMutability":"view"},{"type":"function","name":"GOLDEN_TOUCH_ADDRESS","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"acceptOwnership","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"anchorV4","inputs":[{"name":"_checkpoint","type":"tuple","internalType":"struct ICheckpointStore.Checkpoint","components":[{"name":"blockNumber","type":"uint48","internalType":"uint48"},{"name":"blockHash","type":"bytes32","internalType":"bytes32"},{"name":"stateRoot","type":"bytes32","internalType":"bytes32"}]}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"anchorV4WithSignalSlots","inputs":[{"name":"_checkpoint","type":"tuple","internalType":"struct ICheckpointStore.Checkpoint","components":[{"name":"blockNumber","type":"uint48","internalType":"uint48"},{"name":"blockHash","type":"bytes32","internalType":"bytes32"},{"name":"stateRoot","type":"bytes32","internalType":"bytes32"}]},{"name":"_signalSlots","type":"bytes32[]","internalType":"bytes32[]"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"anchorV5","inputs":[{"name":"_proposalParams","type":"tuple","internalType":"struct Anchor.ProposalParams","components":[{"name":"submissionWindowEnd","type":"uint48","internalType":"uint48"}]},{"name":"_blockParams","type":"tuple","internalType":"struct Anchor.BlockParams","components":[{"name":"anchorBlockNumber","type":"uint48","internalType":"uint48"},{"name":"anchorBlockHash","type":"bytes32","internalType":"bytes32"},{"name":"anchorStateRoot","type":"bytes32","internalType":"bytes32"},{"name":"rawTxListHash","type":"bytes32","internalType":"bytes32"}]}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"blockHashes","inputs":[{"name":"blockNumber","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"blockHash","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"checkpointStore","inputs":[],"outputs":[{"name":"","type":"address","internalType":"contract ICheckpointStore"}],"stateMutability":"view"},{"type":"function","name":"getBlockState","inputs":[],"outputs":[{"name":"","type":"tuple","internalType":"struct Anchor.BlockState","components":[{"name":"anchorBlockNumber","type":"uint48","internalType":"uint48"},{"name":"ancestorsHash","type":"bytes32","internalType":"bytes32"}]}],"stateMutability":"view"},{"type":"function","name":"getPreconfMetadata","inputs":[{"name":"_blockNumber","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"tuple","internalType":"struct Anchor.PreconfMetadata","components":[{"name":"anchorBlockNumber","type":"uint48","internalType":"uint48"},{"name":"submissionWindowEnd","type":"uint48","internalType":"uint48"},{"name":"parentSubmissionWindowEnd","type":"uint48","internalType":"uint48"},{"name":"rawTxListHash","type":"bytes32","internalType":"bytes32"},{"name":"parentRawTxListHash","type":"bytes32","internalType":"bytes32"}]}],"stateMutability":"view"},{"type":"function","name":"impl","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"inNonReentrant","inputs":[],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"init","inputs":[{"name":"_owner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"l1ChainId","inputs":[],"outputs":[{"name":"","type":"uint64","internalType":"uint64"}],"stateMutability":"view"},{"type":"function","name":"owner","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"pause","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"paused","inputs":[],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"pendingOwner","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"proxiableUUID","inputs":[],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"renounceOwnership","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"resolver","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"transferOwnership","inputs":[{"name":"newOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"unpause","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"upgradeTo","inputs":[{"name":"newImplementation","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"upgradeToAndCall","inputs":[{"name":"newImplementation","type":"address","internalType":"address"},{"name":"data","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"payable"},{"type":"function","name":"withdraw","inputs":[{"name":"_token","type":"address","internalType":"address"},{"name":"_to","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"AdminChanged","inputs":[{"name":"previousAdmin","type":"address","indexed":false,"internalType":"address"},{"name":"newAdmin","type":"address","indexed":false,"internalType":"address"}],"anonymous":false},{"type":"event","name":"Anchored","inputs":[{"name":"prevAnchorBlockNumber","type":"uint48","indexed":false,"internalType":"uint48"},{"name":"anchorBlockNumber","type":"uint48","indexed":false,"internalType":"uint48"},{"name":"ancestorsHash","type":"bytes32","indexed":false,"internalType":"bytes32"}],"anonymous":false},{"type":"event","name":"BeaconUpgraded","inputs":[{"name":"beacon","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"Initialized","inputs":[{"name":"version","type":"uint8","indexed":false,"internalType":"uint8"}],"anonymous":false},{"type":"event","name":"OwnershipTransferStarted","inputs":[{"name":"previousOwner","type":"address","indexed":true,"internalType":"address"},{"name":"newOwner","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"OwnershipTransferred","inputs":[{"name":"previousOwner","type":"address","indexed":true,"internalType":"address"},{"name":"newOwner","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"Paused","inputs":[{"name":"account","type":"address","indexed":false,"internalType":"address"}],"anonymous":false},{"type":"event","name":"Unpaused","inputs":[{"name":"account","type":"address","indexed":false,"internalType":"address"}],"anonymous":false},{"type":"event","name":"Upgraded","inputs":[{"name":"implementation","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"Withdrawn","inputs":[{"name":"token","type":"address","indexed":false,"internalType":"address"},{"name":"to","type":"address","indexed":false,"internalType":"address"},{"name":"amount","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"error","name":"ACCESS_DENIED","inputs":[]},{"type":"error","name":"AncestorsHashMismatch","inputs":[]},{"type":"error","name":"ETH_TRANSFER_FAILED","inputs":[]},{"type":"error","name":"FUNC_NOT_IMPLEMENTED","inputs":[]},{"type":"error","name":"INVALID_PAUSE_STATUS","inputs":[]},{"type":"error","name":"InvalidAddress","inputs":[]},{"type":"error","name":"InvalidBlockNumber","inputs":[]},{"type":"error","name":"InvalidL1ChainId","inputs":[]},{"type":"error","name":"InvalidL2ChainId","inputs":[]},{"type":"error","name":"InvalidSender","inputs":[]},{"type":"error","name":"REENTRANT_CALL","inputs":[]},{"type":"error","name":"ZERO_ADDRESS","inputs":[]},{"type":"error","name":"ZERO_VALUE","inputs":[]}]} \ No newline at end of file diff --git a/realtime/src/l2/bindings.rs b/realtime/src/l2/bindings.rs new file mode 100644 index 00000000..690f8934 --- /dev/null +++ b/realtime/src/l2/bindings.rs @@ -0,0 +1,10 @@ +#![allow(clippy::too_many_arguments)] + +use alloy::sol; + +sol!( + #[allow(missing_docs)] + #[sol(rpc)] + Anchor, + "src/l2/abi/Anchor.json" +); diff --git a/realtime/src/l2/execution_layer.rs b/realtime/src/l2/execution_layer.rs new file mode 100644 index 00000000..9811fda4 --- /dev/null +++ b/realtime/src/l2/execution_layer.rs @@ -0,0 +1,487 @@ +use crate::l2::bindings::{Anchor, ICheckpointStore::Checkpoint}; +use crate::shared_abi::bindings::{ + Bridge::{self, MessageSent}, + HopProof, + IBridge::Message, + SignalService::SignalSent, +}; +use alloy::{ + consensus::{ + SignableTransaction, Transaction as AnchorTransaction, TxEnvelope, transaction::Recovered, + }, + primitives::{Address, B256, Bytes, FixedBytes}, + providers::{DynProvider, Provider}, + rpc::types::Transaction, + signers::{Signature, Signer as AlloySigner}, + sol_types::SolEvent, +}; +use anyhow::Error; +use common::shared::{ + alloy_tools, execution_layer::ExecutionLayer as ExecutionLayerCommon, + l2_slot_info_v2::L2SlotInfoV2, +}; +use common::{ + crypto::{GOLDEN_TOUCH_ADDRESS, GOLDEN_TOUCH_PRIVATE_KEY}, + signer::Signer, +}; +use pacaya::l2::config::TaikoConfig; +use std::sync::Arc; +use tracing::{debug, info, warn}; + +pub struct L2ExecutionLayer { + common: ExecutionLayerCommon, + pub provider: DynProvider, + anchor: Anchor::AnchorInstance, + pub bridge: Bridge::BridgeInstance, + pub signal_service: Address, + pub chain_id: u64, + #[allow(dead_code)] + pub config: TaikoConfig, + l2_call_signer: Arc, +} + +impl L2ExecutionLayer { + pub async fn new(taiko_config: TaikoConfig) -> Result { + let provider = + alloy_tools::create_alloy_provider_without_wallet(&taiko_config.taiko_geth_url).await?; + + let chain_id = provider + .get_chain_id() + .await + .map_err(|e| anyhow::anyhow!("Failed to get chain ID: {}", e))?; + info!("L2 Chain ID: {}", chain_id); + + let anchor = Anchor::new(taiko_config.taiko_anchor_address, provider.clone()); + + let chain_id_string = format!("{}", chain_id); + let zeros_needed = 38usize.saturating_sub(chain_id_string.len()); + let bridge_address: Address = + format!("0x{}{}01", chain_id_string, "0".repeat(zeros_needed)).parse()?; + let bridge = Bridge::new(bridge_address, provider.clone()); + + let signal_service: Address = + format!("0x{}{}05", chain_id_string, "0".repeat(zeros_needed)).parse()?; + + let common = + ExecutionLayerCommon::new(provider.clone(), taiko_config.signer.get_address()).await?; + let l2_call_signer = taiko_config.signer.clone(); + + Ok(Self { + common, + provider, + anchor, + bridge, + signal_service, + chain_id, + l2_call_signer, + config: taiko_config, + }) + } + + pub fn common(&self) -> &ExecutionLayerCommon { + &self.common + } + + pub async fn construct_anchor_tx( + &self, + l2_slot_info: &L2SlotInfoV2, + anchor_block_params: (Checkpoint, Vec>), + ) -> Result { + debug!( + "Constructing anchor transaction for block number: {}", + l2_slot_info.parent_id() + 1 + ); + let nonce = self + .provider + .get_transaction_count(GOLDEN_TOUCH_ADDRESS) + .block_id((*l2_slot_info.parent_hash()).into()) + .await + .map_err(|e| { + self.common + .chain_error("Failed to get transaction count", Some(&e.to_string())) + })?; + + let call_builder = self + .anchor + .anchorV4WithSignalSlots(anchor_block_params.0, anchor_block_params.1) + .gas(1_000_000) + .max_fee_per_gas(u128::from(l2_slot_info.base_fee())) + .max_priority_fee_per_gas(0) + .nonce(nonce) + .chain_id(self.chain_id); + + let typed_tx = call_builder + .into_transaction_request() + .build_typed_tx() + .map_err(|_| anyhow::anyhow!("AnchorTX: Failed to build typed transaction"))?; + + let tx_eip1559 = typed_tx + .eip1559() + .ok_or_else(|| anyhow::anyhow!("AnchorTX: Failed to extract EIP-1559 transaction"))?; + + let signature = self.sign_hash_deterministic(tx_eip1559.signature_hash())?; + let sig_tx = tx_eip1559.clone().into_signed(signature); + + let tx_envelope = TxEnvelope::from(sig_tx); + + debug!("AnchorTX transaction hash: {}", tx_envelope.tx_hash()); + + let tx = Transaction { + inner: Recovered::new_unchecked(tx_envelope, GOLDEN_TOUCH_ADDRESS), + block_hash: None, + block_number: None, + transaction_index: None, + effective_gas_price: None, + }; + Ok(tx) + } + + fn sign_hash_deterministic(&self, hash: B256) -> Result { + common::crypto::fixed_k_signer::sign_hash_deterministic(GOLDEN_TOUCH_PRIVATE_KEY, hash) + } + + pub async fn transfer_eth_from_l2_to_l1( + &self, + _amount: u128, + _dest_chain_id: u64, + _preconfer_address: Address, + _bridge_relayer_fee: u64, + ) -> Result<(), Error> { + warn!("Implement bridge transfer logic here"); + Ok(()) + } + + pub async fn get_last_synced_anchor_block_id_from_geth(&self) -> Result { + self.get_latest_anchor_transaction_input() + .await + .map_err(|e| anyhow::anyhow!("get_last_synced_anchor_block_id_from_geth: {e}")) + .and_then(|input| Self::decode_anchor_id_from_tx_data(&input)) + } + + async fn get_latest_anchor_transaction_input(&self) -> Result, Error> { + let block = self.common.get_latest_block_with_txs().await?; + let anchor_tx = match block.transactions.as_transactions() { + Some(txs) => txs.first().ok_or_else(|| { + anyhow::anyhow!( + "get_latest_anchor_transaction_input: Cannot get anchor transaction from block {}", + block.number() + ) + })?, + None => { + return Err(anyhow::anyhow!( + "No transactions in L2 block {}", + block.number() + )); + } + }; + + Ok(anchor_tx.input().to_vec()) + } + + pub fn decode_anchor_id_from_tx_data(data: &[u8]) -> Result { + let tx_data = + ::abi_decode_validate( + data, + ) + .map_err(|e| anyhow::anyhow!("Failed to decode anchor id from tx data: {}", e))?; + Ok(tx_data._checkpoint.blockNumber.to::()) + } + + pub fn get_anchor_tx_data(data: &[u8]) -> Result { + let tx_data = + ::abi_decode_validate( + data, + ) + .map_err(|e| anyhow::anyhow!("Failed to decode anchor tx data: {}", e))?; + Ok(tx_data) + } + + #[allow(dead_code)] + pub async fn get_head_l1_origin(&self) -> Result { + let response = self + .provider + .raw_request::<_, serde_json::Value>( + std::borrow::Cow::Borrowed("taiko_headL1Origin"), + (), + ) + .await + .map_err(|e| anyhow::anyhow!("Failed to fetch taiko_headL1Origin: {}", e))?; + + let hex_str = response + .get("blockID") + .or_else(|| response.get("blockId")) + .and_then(serde_json::Value::as_str) + .ok_or_else(|| { + anyhow::anyhow!("Missing or invalid block id in taiko_headL1Origin response") + })?; + + u64::from_str_radix(hex_str.trim_start_matches("0x"), 16) + .map_err(|e| anyhow::anyhow!("Failed to parse 'blockID' as u64: {}", e)) + } + + #[allow(dead_code)] + pub async fn get_last_synced_block_params_from_geth(&self) -> Result { + self.get_latest_anchor_transaction_input() + .await + .map_err(|e| anyhow::anyhow!("get_last_synced_block_params_from_geth: {e}")) + .and_then(|input| Self::decode_block_params_from_tx_data(&input)) + } + + #[allow(dead_code)] + pub fn decode_block_params_from_tx_data(data: &[u8]) -> Result { + let tx_data = + ::abi_decode_validate( + data, + ) + .map_err(|e| anyhow::anyhow!("Failed to decode block params from tx data: {}", e))?; + Ok(tx_data._checkpoint) + } +} + +// Surge: L2 UserOp execution + +use crate::node::proposal_manager::bridge_handler::UserOp; + +impl L2ExecutionLayer { + /// Construct a signed L2 transaction that executes a UserOp on L2 + /// by forwarding the calldata to the submitter smart wallet. + pub async fn construct_l2_user_op_tx(&self, user_op: &UserOp) -> Result { + use alloy::signers::local::PrivateKeySigner; + use std::str::FromStr; + + debug!( + "Constructing L2 UserOp execution tx for submitter={}", + user_op.submitter + ); + + let signer_address = self.l2_call_signer.get_address(); + + let nonce = self + .provider + .get_transaction_count(signer_address) + .await + .map_err(|e| anyhow::anyhow!("Failed to get nonce for L2 UserOp tx: {}", e))?; + + let typed_tx = alloy::consensus::TxEip1559 { + chain_id: self.chain_id, + nonce, + gas_limit: 3_000_000, + max_fee_per_gas: 1_000_000_000, + max_priority_fee_per_gas: 0, + to: alloy::primitives::TxKind::Call(user_op.submitter), + value: alloy::primitives::U256::ZERO, + input: user_op.calldata.clone(), + access_list: Default::default(), + }; + + let signature = match self.l2_call_signer.as_ref() { + Signer::Web3signer(web3signer, address) => { + let signature_bytes = web3signer.sign_transaction(&typed_tx, *address).await?; + Signature::try_from(signature_bytes.as_slice()) + .map_err(|e| anyhow::anyhow!("Failed to parse signature: {}", e))? + } + Signer::PrivateKey(private_key, _) => { + let signer = PrivateKeySigner::from_str(private_key.as_str())?; + AlloySigner::sign_hash(&signer, &typed_tx.signature_hash()).await? + } + }; + + let sig_tx = typed_tx.into_signed(signature); + let tx_envelope = TxEnvelope::from(sig_tx); + + debug!("L2 UserOp execution tx hash: {}", tx_envelope.tx_hash()); + + // SAFETY: `new_unchecked` is safe here because we just signed `tx_envelope` with + // `l2_call_signer` and `signer_address` is derived from the same key. + let tx = Transaction { + inner: Recovered::new_unchecked(tx_envelope, signer_address), + block_hash: None, + block_number: None, + transaction_index: None, + effective_gas_price: None, + }; + Ok(tx) + } +} + +// Surge: L2 EL ops for Bridge Handler + +pub trait L2BridgeHandlerOps { + async fn construct_l2_call_tx(&self, message: Message) -> Result; + async fn find_message_and_signal_slot( + &self, + block_id: u64, + ) -> Result)>, anyhow::Error>; + async fn get_hop_proof( + &self, + slot: FixedBytes<32>, + block_id: u64, + state_root: B256, + ) -> Result; +} + +impl L2BridgeHandlerOps for L2ExecutionLayer { + async fn construct_l2_call_tx(&self, message: Message) -> Result { + use alloy::signers::local::PrivateKeySigner; + use std::str::FromStr; + + debug!("Constructing bridge call transaction for L2 call"); + + let signer_address = self.l2_call_signer.get_address(); + + let nonce = self + .provider + .get_transaction_count(signer_address) + .await + .map_err(|e| anyhow::anyhow!("Failed to get nonce for bridge call: {}", e))?; + + let call_builder = self + .bridge + .processMessage(message, Bytes::new()) + .gas(3_000_000) + .max_fee_per_gas(1_000_000_000) + .max_priority_fee_per_gas(0) + .nonce(nonce) + .chain_id(self.chain_id); + + let typed_tx = call_builder + .into_transaction_request() + .build_typed_tx() + .map_err(|_| anyhow::anyhow!("L2 Call Tx: Failed to build typed transaction"))?; + + let tx_eip1559 = typed_tx + .eip1559() + .ok_or_else(|| anyhow::anyhow!("L2 Call Tx: Failed to extract EIP-1559 transaction"))? + .clone(); + + let signature = match self.l2_call_signer.as_ref() { + Signer::Web3signer(web3signer, address) => { + let signature_bytes = web3signer.sign_transaction(&tx_eip1559, *address).await?; + Signature::try_from(signature_bytes.as_slice()) + .map_err(|e| anyhow::anyhow!("Failed to parse signature: {}", e))? + } + Signer::PrivateKey(private_key, _) => { + let signer = PrivateKeySigner::from_str(private_key.as_str())?; + AlloySigner::sign_hash(&signer, &tx_eip1559.signature_hash()).await? + } + }; + + let sig_tx = tx_eip1559.into_signed(signature); + let tx_envelope = TxEnvelope::from(sig_tx); + + debug!("L2 Call transaction hash: {}", tx_envelope.tx_hash()); + + let tx = Transaction { + inner: Recovered::new_unchecked(tx_envelope, signer_address), + block_hash: None, + block_number: None, + transaction_index: None, + effective_gas_price: None, + }; + Ok(tx) + } + + async fn find_message_and_signal_slot( + &self, + block_id: u64, + ) -> Result)>, anyhow::Error> { + use alloy::rpc::types::Filter; + + let bridge_address = *self.bridge.address(); + let signal_service_address = self.signal_service; + + let filter = Filter::new().from_block(block_id).to_block(block_id); + + let bridge_filter = filter + .clone() + .address(bridge_address) + .event_signature(MessageSent::SIGNATURE_HASH); + + let bridge_logs = self + .provider + .get_logs(&bridge_filter) + .await + .map_err(|e| anyhow::anyhow!("Failed to get MessageSent logs from bridge: {e}"))?; + + let signal_filter = filter + .address(signal_service_address) + .event_signature(SignalSent::SIGNATURE_HASH); + + let signal_logs = self.provider.get_logs(&signal_filter).await.map_err(|e| { + anyhow::anyhow!("Failed to get SignalSent logs from signal service: {e}") + })?; + + if bridge_logs.is_empty() || signal_logs.is_empty() { + return Ok(None); + } + + let message = { + let log = bridge_logs + .first() + .ok_or_else(|| anyhow::anyhow!("No bridge logs"))?; + let log_data = alloy::primitives::LogData::new_unchecked( + log.topics().to_vec(), + log.data().data.clone(), + ); + MessageSent::decode_log_data(&log_data) + .map_err(|e| anyhow::anyhow!("Failed to decode MessageSent event: {e}"))? + .message + }; + + let slot = { + let log = signal_logs + .first() + .ok_or_else(|| anyhow::anyhow!("No signal logs"))?; + let log_data = alloy::primitives::LogData::new_unchecked( + log.topics().to_vec(), + log.data().data.clone(), + ); + SignalSent::decode_log_data(&log_data) + .map_err(|e| anyhow::anyhow!("Failed to decode SignalSent event: {e}"))? + .slot + }; + + Ok(Some((message, slot))) + } + + async fn get_hop_proof( + &self, + slot: FixedBytes<32>, + block_id: u64, + state_root: B256, + ) -> Result { + use alloy::sol_types::SolValue; + + let proof = self + .provider + .get_proof(self.signal_service, vec![slot]) + .block_id(block_id.into()) + .await + .map_err(|e| anyhow::anyhow!("eth_getProof failed for signal slot: {e}"))?; + + let storage_proof = proof + .storage_proof + .first() + .ok_or_else(|| anyhow::anyhow!("No storage proof returned for signal slot"))?; + + let hop_proof = HopProof { + chainId: self.chain_id, + blockId: block_id, + rootHash: state_root, + cacheOption: 0, + accountProof: proof.account_proof.clone(), + storageProof: storage_proof.proof.clone(), + }; + + info!( + "Built HopProof: chainId={}, blockId={}, rootHash={}, accountProof_len={}, storageProof_len={}", + hop_proof.chainId, + hop_proof.blockId, + hop_proof.rootHash, + hop_proof.accountProof.len(), + hop_proof.storageProof.len(), + ); + + Ok(Bytes::from(vec![hop_proof].abi_encode_params())) + } +} diff --git a/realtime/src/l2/mod.rs b/realtime/src/l2/mod.rs new file mode 100644 index 00000000..b0e580e0 --- /dev/null +++ b/realtime/src/l2/mod.rs @@ -0,0 +1,3 @@ +pub mod bindings; +pub mod execution_layer; +pub mod taiko; diff --git a/realtime/src/l2/taiko.rs b/realtime/src/l2/taiko.rs new file mode 100644 index 00000000..2649d8d2 --- /dev/null +++ b/realtime/src/l2/taiko.rs @@ -0,0 +1,362 @@ +#![allow(dead_code)] + +use super::execution_layer::L2ExecutionLayer; +use crate::l1::protocol_config::ProtocolConfig; +use crate::l2::bindings::{Anchor, ICheckpointStore::Checkpoint}; +use crate::node::proposal_manager::l2_block_payload::L2BlockV2Payload; +use alloy::primitives::FixedBytes; +use alloy::{ + consensus::BlockHeader, + eips::BlockNumberOrTag, + primitives::{Address, B256}, + rpc::types::Block, +}; +use anyhow::Error; +use common::shared::l2_slot_info_v2::L2SlotContext; +use common::{ + l1::slot_clock::SlotClock, + l2::{ + engine::L2Engine, + taiko_driver::{ + OperationType, TaikoDriver, TaikoDriverConfig, + models::{BuildPreconfBlockRequestBody, BuildPreconfBlockResponse, ExecutableData}, + }, + traits::Bridgeable, + }, + metrics::Metrics, + shared::{ + l2_slot_info_v2::L2SlotInfoV2, + l2_tx_lists::{self, PreBuiltTxList}, + }, +}; +use pacaya::l2::config::TaikoConfig; +use std::sync::Arc; +use taiko_alethia_reth::validation::ANCHOR_V3_V4_GAS_LIMIT; +use tracing::{debug, trace}; + +pub struct Taiko { + protocol_config: ProtocolConfig, + l2_execution_layer: Arc, + driver: Arc, + slot_clock: Arc, + coinbase: String, + l2_engine: L2Engine, +} + +impl Taiko { + pub async fn new( + slot_clock: Arc, + protocol_config: ProtocolConfig, + metrics: Arc, + taiko_config: TaikoConfig, + l2_engine: L2Engine, + ) -> Result { + let driver_config: TaikoDriverConfig = TaikoDriverConfig { + driver_url: taiko_config.driver_url.clone(), + rpc_driver_preconf_timeout: taiko_config.rpc_driver_preconf_timeout, + rpc_driver_status_timeout: taiko_config.rpc_driver_status_timeout, + rpc_driver_retry_timeout: taiko_config.rpc_driver_retry_timeout, + jwt_secret_bytes: taiko_config.jwt_secret_bytes, + }; + Ok(Self { + protocol_config, + l2_execution_layer: Arc::new( + L2ExecutionLayer::new(taiko_config.clone()) + .await + .map_err(|e| anyhow::anyhow!("Failed to create L2ExecutionLayer: {}", e))?, + ), + driver: Arc::new(TaikoDriver::new(&driver_config, metrics).await?), + slot_clock, + coinbase: format!("0x{}", hex::encode(taiko_config.signer.get_address())), + l2_engine, + }) + } + + pub fn get_driver(&self) -> Arc { + self.driver.clone() + } + + pub fn l2_execution_layer(&self) -> Arc { + self.l2_execution_layer.clone() + } + + pub async fn get_pending_l2_tx_list_from_l2_engine( + &self, + base_fee: u64, + batches_ready_to_send: u64, + gas_limit: u64, + ) -> Result, Error> { + self.l2_engine + .get_pending_l2_tx_list(base_fee, batches_ready_to_send, gas_limit) + .await + } + + pub fn get_protocol_config(&self) -> &ProtocolConfig { + &self.protocol_config + } + + pub async fn get_latest_l2_block_id(&self) -> Result { + self.l2_execution_layer.common().get_latest_block_id().await + } + + pub async fn get_l2_block_by_number( + &self, + number: u64, + full_txs: bool, + ) -> Result { + self.l2_execution_layer + .common() + .get_block_by_number(number, full_txs) + .await + } + + pub async fn fetch_l2_blocks_until_latest( + &self, + start_block: u64, + full_txs: bool, + ) -> Result, Error> { + let start_time = std::time::Instant::now(); + let end_block = self.get_latest_l2_block_id().await?; + let mut blocks = Vec::with_capacity(usize::try_from(end_block - start_block + 1)?); + for block_number in start_block..=end_block { + let block = self.get_l2_block_by_number(block_number, full_txs).await?; + blocks.push(block); + } + debug!( + "Fetched L2 blocks from {} to {} in {} ms", + start_block, + end_block, + start_time.elapsed().as_millis() + ); + Ok(blocks) + } + + pub async fn get_transaction_by_hash( + &self, + hash: B256, + ) -> Result { + self.l2_execution_layer + .common() + .get_transaction_by_hash(hash) + .await + } + + pub async fn get_l2_block_hash(&self, number: u64) -> Result { + self.l2_execution_layer + .common() + .get_block_hash(number) + .await + } + + /// Scan backward from L2 head to find the block number matching a given hash. + /// Used during recovery to resolve `lastFinalizedBlockHash` from L1 to an L2 block number. + pub async fn find_l2_block_number_by_hash(&self, block_hash: B256) -> Result { + let head = self.get_latest_l2_block_id().await?; + for n in (0..=head).rev() { + let hash = self.get_l2_block_hash(n).await?; + if hash == block_hash { + return Ok(n); + } + } + Err(anyhow::anyhow!( + "L2 block with hash {} not found on Geth (scanned {} blocks)", + block_hash, + head + 1 + )) + } + + pub async fn get_l2_slot_info(&self) -> Result { + self.get_l2_slot_info_by_parent_block(BlockNumberOrTag::Latest) + .await + } + + pub async fn get_l2_slot_info_by_parent_block( + &self, + parent: BlockNumberOrTag, + ) -> Result { + let l2_slot_timestamp = self.slot_clock.get_l2_slot_begin_timestamp()?; + let parent_block = self + .l2_execution_layer + .common() + .get_block_header(parent) + .await?; + let parent_id = parent_block.header.number(); + let parent_hash = parent_block.header.hash; + let parent_gas_limit = parent_block.header.gas_limit(); + let parent_timestamp = parent_block.header.timestamp(); + + let parent_gas_limit_without_anchor = if parent_id != 0 { + parent_gas_limit + .checked_sub(ANCHOR_V3_V4_GAS_LIMIT) + .ok_or_else(|| { + anyhow::anyhow!( + "parent_gas_limit {} is less than ANCHOR_V3_V4_GAS_LIMIT {}", + parent_gas_limit, + ANCHOR_V3_V4_GAS_LIMIT + ) + })? + } else { + parent_gas_limit + }; + + let base_fee: u64 = self.get_base_fee(parent_block).await?; + + trace!( + timestamp = %l2_slot_timestamp, + parent_hash = %parent_hash, + parent_gas_limit_without_anchor = %parent_gas_limit_without_anchor, + parent_timestamp = %parent_timestamp, + base_fee = %base_fee, + "L2 slot info" + ); + + Ok(L2SlotInfoV2::new( + base_fee, + l2_slot_timestamp, + parent_id, + parent_hash, + parent_gas_limit_without_anchor, + parent_timestamp, + )) + } + + async fn get_base_fee(&self, parent_block: Block) -> Result { + if parent_block.header.number() == 0 { + return Ok(taiko_alethia_reth::eip4396::SHASTA_INITIAL_BASE_FEE); + } + + let grandparent_number = parent_block.header.number() - 1; + let grandparent_timestamp = self + .l2_execution_layer + .common() + .get_block_header(BlockNumberOrTag::Number(grandparent_number)) + .await? + .header + .timestamp(); + + let timestamp_diff = parent_block + .header + .timestamp() + .checked_sub(grandparent_timestamp) + .ok_or_else(|| anyhow::anyhow!("Timestamp underflow occurred"))?; + + let parent_base_fee_per_gas = + parent_block.header.inner.base_fee_per_gas.ok_or_else(|| { + anyhow::anyhow!( + "get_base_fee: Parent block missing base fee per gas for block {}", + parent_block.header.number() + ) + })?; + let base_fee = taiko_alethia_reth::eip4396::calculate_next_block_eip4396_base_fee( + &parent_block.header.inner, + timestamp_diff, + parent_base_fee_per_gas, + taiko_protocol::shasta::constants::min_base_fee_for_chain( + self.l2_execution_layer.common().chain_id(), + ), + ); + + Ok(base_fee) + } + + #[allow(clippy::too_many_arguments)] + pub async fn advance_head_to_new_l2_block( + &self, + l2_block_payload: L2BlockV2Payload, + l2_slot_context: &L2SlotContext, + anchor_signal_slots: Vec>, + operation_type: OperationType, + ) -> Result { + tracing::debug!( + "Submitting new L2 block to the Taiko driver with {} txs", + l2_block_payload.tx_list.len() + ); + + let anchor_block_params = ( + Checkpoint { + blockNumber: l2_block_payload.anchor_block_id.try_into()?, + blockHash: l2_block_payload.anchor_block_hash, + stateRoot: l2_block_payload.anchor_state_root, + }, + anchor_signal_slots, + ); + + let anchor_tx = self + .l2_execution_layer + .construct_anchor_tx(&l2_slot_context.info, anchor_block_params) + .await + .map_err(|e| { + anyhow::anyhow!( + "advance_head_to_new_l2_block: Failed to construct anchor tx: {}", + e + ) + })?; + let tx_list = std::iter::once(anchor_tx) + .chain(l2_block_payload.tx_list.into_iter()) + .collect::>(); + + let tx_list_bytes = l2_tx_lists::encode_and_compress(&tx_list)?; + + let sharing_pctg = self.protocol_config.get_basefee_sharing_pctg(); + + // RealTime: 7 bytes — basefee_sharing_pctg + 6 zero bytes (no proposal_id) + let extra_data = format!("0x{:02x}000000000000", sharing_pctg); + + let executable_data = ExecutableData { + base_fee_per_gas: l2_slot_context.info.base_fee(), + block_number: l2_slot_context.info.parent_id() + 1, + extra_data, + fee_recipient: l2_block_payload.coinbase.to_string(), + gas_limit: l2_block_payload.gas_limit_without_anchor + ANCHOR_V3_V4_GAS_LIMIT, + parent_hash: format!("0x{}", hex::encode(l2_slot_context.info.parent_hash())), + timestamp: l2_block_payload.timestamp_sec, + transactions: format!("0x{}", hex::encode(tx_list_bytes)), + }; + + let request_body = BuildPreconfBlockRequestBody { + executable_data, + end_of_sequencing: l2_slot_context.end_of_sequencing, + is_forced_inclusion: false, + }; + + self.driver + .preconf_blocks(request_body, operation_type) + .await + } + + pub async fn reorg_stale_block( + &self, + new_head_block_number: u64, + ) -> Result { + self.driver.reorg_stale_block(new_head_block_number).await + } + + pub fn decode_anchor_id_from_tx_data(data: &[u8]) -> Result { + L2ExecutionLayer::decode_anchor_id_from_tx_data(data) + } + + pub fn get_anchor_tx_data(data: &[u8]) -> Result { + L2ExecutionLayer::get_anchor_tx_data(data) + } +} + +impl Bridgeable for Taiko { + async fn get_balance(&self, address: Address) -> Result { + self.l2_execution_layer + .common() + .get_account_balance(address) + .await + } + + async fn transfer_eth_from_l2_to_l1( + &self, + amount: u128, + dest_chain_id: u64, + address: Address, + bridge_relayer_fee: u64, + ) -> Result<(), Error> { + self.l2_execution_layer + .transfer_eth_from_l2_to_l1(amount, dest_chain_id, address, bridge_relayer_fee) + .await + } +} diff --git a/realtime/src/lib.rs b/realtime/src/lib.rs new file mode 100644 index 00000000..9c6ddf3e --- /dev/null +++ b/realtime/src/lib.rs @@ -0,0 +1,173 @@ +mod chain_monitor; +mod l1; +mod l2; +mod node; +pub mod raiko; +mod shared_abi; +mod utils; + +use crate::utils::config::RealtimeConfig; +use anyhow::Error; +use common::{ + batch_builder::BatchBuilderConfig, + config::Config, + config::ConfigTrait, + fork_info::ForkInfo, + l1::{self as common_l1, traits::PreconferProvider}, + l2::engine::{L2Engine, L2EngineConfig}, + metrics, + utils::cancellation_token::CancellationToken, +}; +use l1::execution_layer::ExecutionLayer; +use node::Node; +use std::sync::Arc; +use tokio::sync::mpsc; +use tracing::info; + +pub async fn create_realtime_node( + config: Config, + metrics: Arc, + cancel_token: CancellationToken, + fork_info: ForkInfo, +) -> Result<(), Error> { + info!("Creating RealTime node"); + + let realtime_config = RealtimeConfig::read_env_variables() + .map_err(|e| anyhow::anyhow!("Failed to read RealTime configuration: {}", e))?; + info!("RealTime config: {}", realtime_config); + + let (transaction_error_sender, transaction_error_receiver) = mpsc::channel(100); + let ethereum_l1 = common_l1::ethereum_l1::EthereumL1::::new( + common_l1::config::EthereumL1Config::new(&config).await?, + l1::config::EthereumL1Config::try_from(realtime_config.clone())?, + transaction_error_sender, + metrics.clone(), + ) + .await + .map_err(|e| anyhow::anyhow!("Failed to create EthereumL1: {}", e))?; + + let ethereum_l1 = Arc::new(ethereum_l1); + + let taiko_config = pacaya::l2::config::TaikoConfig::new(&config) + .await + .map_err(|e| anyhow::anyhow!("Failed to create TaikoConfig: {}", e))?; + + let l2_engine = L2Engine::new(L2EngineConfig::new( + &config, + taiko_config.signer.get_address(), + )?) + .map_err(|e| anyhow::anyhow!("Failed to create L2Engine: {}", e))?; + let protocol_config = ethereum_l1.execution_layer.fetch_protocol_config().await?; + + let taiko = crate::l2::taiko::Taiko::new( + ethereum_l1.slot_clock.clone(), + protocol_config.clone(), + metrics.clone(), + taiko_config, + l2_engine, + ) + .await?; + let taiko = Arc::new(taiko); + + let node_config = pacaya::node::config::NodeConfig { + preconf_heartbeat_ms: config.preconf_heartbeat_ms, + handover_window_slots: 8, + handover_start_buffer_ms: 500, + l1_height_lag: 8, + propose_forced_inclusion: false, + simulate_not_submitting_at_the_end_of_epoch: false, + watchdog_max_counter: config.watchdog_max_counter, + }; + + let max_blocks_per_batch = if config.max_blocks_per_batch == 0 { + taiko_protocol::shasta::constants::DERIVATION_SOURCE_MAX_BLOCKS.try_into()? + } else { + config.max_blocks_per_batch + }; + + // Use 256-block limit for anchor offset + let max_anchor_height_offset = 256u64; + + let batch_builder_config = BatchBuilderConfig { + max_bytes_size_of_batch: config.max_bytes_size_of_batch, + max_blocks_per_batch, + l1_slot_duration_sec: config.l1_slot_duration_sec, + max_time_shift_between_blocks_sec: config.max_time_shift_between_blocks_sec, + max_anchor_height_offset: max_anchor_height_offset + - config.max_anchor_height_offset_reduction, + default_coinbase: ethereum_l1.execution_layer.get_preconfer_address(), + preconf_min_txs: config.preconf_min_txs, + preconf_max_skipped_l2_slots: config.preconf_max_skipped_l2_slots, + proposal_max_time_sec: config.proposal_max_time_sec, + }; + + // Initialize chain monitor for ProposedAndProved events + let chain_monitor = Arc::new( + chain_monitor::RealtimeChainMonitor::new( + config + .l1_rpc_urls + .first() + .ok_or_else(|| anyhow::anyhow!("L1 RPC URL is required"))? + .clone(), + config.taiko_geth_rpc_url.clone(), + realtime_config.realtime_inbox, + cancel_token.clone(), + "ProposedAndProved", + chain_monitor::print_proposed_and_proved_info, + metrics.clone(), + ) + .map_err(|e| anyhow::anyhow!("Failed to create RealtimeChainMonitor: {}", e))?, + ); + chain_monitor + .start() + .await + .map_err(|e| anyhow::anyhow!("Failed to start RealtimeChainMonitor: {}", e))?; + + // Read the last finalized block hash from L1 + let last_finalized_block_hash = ethereum_l1 + .execution_layer + .get_last_finalized_block_hash() + .await?; + info!( + "Initial lastFinalizedBlockHash: {}", + last_finalized_block_hash + ); + + let preconf_only = realtime_config.preconf_only; + let proof_request_bypass = realtime_config.proof_request_bypass; + let bridge_rpc_addr = realtime_config.bridge_rpc_addr.clone(); + let raiko_client = raiko::RaikoClient::new(&realtime_config); + + let l1_chain_id = { + use common::l1::traits::ELTrait; + ethereum_l1.execution_layer.common().chain_id() + }; + let l2_chain_id = taiko.l2_execution_layer().chain_id; + + let node = Node::new( + node_config, + cancel_token.clone(), + ethereum_l1.clone(), + taiko.clone(), + metrics.clone(), + batch_builder_config, + transaction_error_receiver, + fork_info, + last_finalized_block_hash, + raiko_client, + protocol_config.basefee_sharing_pctg, + preconf_only, + proof_request_bypass, + bridge_rpc_addr, + l1_chain_id, + l2_chain_id, + ) + .await + .map_err(|e| anyhow::anyhow!("Failed to create Node: {}", e))?; + + node.entrypoint() + .await + .map_err(|e| anyhow::anyhow!("Failed to start Node: {}", e))?; + + Ok(()) +} diff --git a/realtime/src/node/mod.rs b/realtime/src/node/mod.rs new file mode 100644 index 00000000..f107f710 --- /dev/null +++ b/realtime/src/node/mod.rs @@ -0,0 +1,525 @@ +pub mod proposal_manager; +use anyhow::Error; +use common::{ + fork_info::ForkInfo, + l1::{ethereum_l1::EthereumL1, transaction_error::TransactionError}, + l2::taiko_driver::{TaikoDriver, models::BuildPreconfBlockResponse}, + metrics::Metrics, + shared::{l2_slot_info_v2::L2SlotContext, l2_tx_lists::PreBuiltTxList}, + utils::{self as common_utils, cancellation_token::CancellationToken}, +}; +use pacaya::node::operator::Status as OperatorStatus; +use pacaya::node::{config::NodeConfig, operator::Operator}; +use std::sync::Arc; +use tracing::{debug, error, info, warn}; + +use crate::l1::execution_layer::ExecutionLayer; +use crate::l2::taiko::Taiko; +use common::batch_builder::BatchBuilderConfig; +use common::l1::traits::PreconferProvider; +use common::shared::head_verifier::HeadVerifier; +use common::shared::l2_slot_info_v2::L2SlotInfoV2; +use proposal_manager::BatchManager; + +use tokio::{ + sync::mpsc::{Receiver, error::TryRecvError}, + time::{Duration, sleep}, +}; + +pub struct Node { + config: NodeConfig, + cancel_token: CancellationToken, + ethereum_l1: Arc>, + taiko: Arc, + watchdog: common_utils::watchdog::Watchdog, + operator: Operator, + #[allow(dead_code)] + metrics: Arc, + proposal_manager: BatchManager, + head_verifier: HeadVerifier, + transaction_error_channel: Receiver, + preconf_only: bool, +} + +impl Node { + #[allow(clippy::too_many_arguments)] + pub async fn new( + config: NodeConfig, + cancel_token: CancellationToken, + ethereum_l1: Arc>, + taiko: Arc, + metrics: Arc, + batch_builder_config: BatchBuilderConfig, + transaction_error_channel: Receiver, + fork_info: ForkInfo, + last_finalized_block_hash: alloy::primitives::B256, + raiko_client: crate::raiko::RaikoClient, + basefee_sharing_pctg: u8, + preconf_only: bool, + proof_request_bypass: bool, + bridge_rpc_addr: String, + l1_chain_id: u64, + l2_chain_id: u64, + ) -> Result { + let operator = Operator::new( + ethereum_l1.execution_layer.clone(), + ethereum_l1.slot_clock.clone(), + taiko.get_driver(), + config.handover_window_slots, + config.handover_start_buffer_ms, + config.simulate_not_submitting_at_the_end_of_epoch, + cancel_token.clone(), + fork_info.clone(), + ) + .map_err(|e| anyhow::anyhow!("Failed to create Operator: {}", e))?; + let watchdog = common_utils::watchdog::Watchdog::new( + cancel_token.clone(), + ethereum_l1.slot_clock.get_l2_slots_per_epoch() / 2, + ); + let head_verifier = HeadVerifier::default(); + + let proposal_manager = BatchManager::new( + config.l1_height_lag, + batch_builder_config, + ethereum_l1.clone(), + taiko.clone(), + metrics.clone(), + cancel_token.clone(), + last_finalized_block_hash, + raiko_client, + basefee_sharing_pctg, + proof_request_bypass, + bridge_rpc_addr, + l1_chain_id, + l2_chain_id, + ) + .await + .map_err(|e| anyhow::anyhow!("Failed to create BatchManager: {}", e))?; + + let start = std::time::Instant::now(); + common::blob::build_default_kzg_settings(); + info!( + "Setup build_default_kzg_settings in {} milliseconds", + start.elapsed().as_millis() + ); + + Ok(Self { + config, + cancel_token, + ethereum_l1, + taiko, + watchdog, + operator, + metrics, + proposal_manager, + head_verifier, + transaction_error_channel, + preconf_only, + }) + } + + pub async fn entrypoint(mut self) -> Result<(), Error> { + info!("Starting RealTime node"); + + if let Err(err) = self.warmup().await { + error!("Failed to warm up node: {}. Shutting down.", err); + self.cancel_token.cancel_on_critical_error(); + return Err(anyhow::anyhow!(err)); + } + + info!("Node warmup successful"); + + tokio::spawn(async move { + self.preconfirmation_loop().await; + }); + + Ok(()) + } + + async fn preconfirmation_loop(&mut self) { + debug!("Main preconfirmation loop started"); + common_utils::synchronization::synchronize_with_l1_slot_start(&self.ethereum_l1).await; + + let mut interval = + tokio::time::interval(Duration::from_millis(self.config.preconf_heartbeat_ms)); + interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + loop { + interval.tick().await; + + if self.cancel_token.is_cancelled() { + info!("Shutdown signal received, exiting main loop..."); + return; + } + + if let Err(err) = self.main_block_preconfirmation_step().await { + error!("Failed to execute main block preconfirmation step: {}", err); + self.watchdog.increment(); + } else { + self.watchdog.reset(); + } + } + } + + async fn main_block_preconfirmation_step(&mut self) -> Result<(), Error> { + let (l2_slot_info, current_status, pending_tx_list) = + self.get_slot_info_and_status().await?; + + if !self.preconf_only { + // Poll for completed async submissions (non-blocking) + if let Some(result) = self.proposal_manager.poll_submission_result() { + match result { + Ok(()) => info!("Async submission completed successfully"), + Err(e) => { + if let Some(transaction_error) = e.downcast_ref::() { + self.handle_transaction_error( + transaction_error, + ¤t_status, + &l2_slot_info, + ) + .await?; + } else { + warn!( + "Async submission failed: {}. Reorging preconfirmed L2 blocks.", + e + ); + self.recover_from_failed_submission().await?; + } + // Return early — l2_slot_info is stale after reorg recovery. + // The next heartbeat will pick up fresh state. + return Ok(()); + } + } + } + + // Check for transaction errors (reverts detected after mining) + match self.transaction_error_channel.try_recv() { + Ok(error) => { + self.handle_transaction_error(&error, ¤t_status, &l2_slot_info) + .await?; + // Return early — l2_slot_info is stale after reorg recovery. + return Ok(()); + } + Err(err) => match err { + TryRecvError::Empty => {} + TryRecvError::Disconnected => { + self.cancel_token.cancel_on_critical_error(); + return Err(anyhow::anyhow!("Transaction error channel disconnected")); + } + }, + } + } + + if current_status.is_preconfirmation_start_slot() { + self.head_verifier + .set(l2_slot_info.parent_id(), *l2_slot_info.parent_hash()) + .await; + } + + // Preconfirmation phase — skip if a proof request or submission is already in progress + if current_status.is_preconfer() + && current_status.is_driver_synced() + && !self.proposal_manager.is_submission_in_progress() + { + if !self + .head_verifier + .verify(l2_slot_info.parent_id(), l2_slot_info.parent_hash()) + .await + { + self.head_verifier.log_error().await; + warn!("Unexpected L2 head detected. Attempting recovery via reorg."); + self.recover_from_failed_submission().await?; + return Ok(()); + } + + let l2_slot_context = L2SlotContext { + info: l2_slot_info.clone(), + end_of_sequencing: current_status.is_end_of_sequencing(), + }; + + if self + .proposal_manager + .should_new_block_be_created(&pending_tx_list, &l2_slot_context) + && (pending_tx_list + .as_ref() + .is_some_and(|pre_built_list| !pre_built_list.tx_list.is_empty()) + || self.proposal_manager.has_pending_user_ops().await) + { + let preconfed_block = self + .proposal_manager + .preconfirm_block(pending_tx_list, &l2_slot_context) + .await?; + + self.verify_preconfed_block(preconfed_block).await?; + } + } + + // Submission phase + if self.preconf_only { + // PRECONF_ONLY mode: drop finalized batches without proving/proposing + self.proposal_manager.drain_finalized_batches(); + } else if current_status.is_submitter() + && !self.proposal_manager.is_submission_in_progress() + && let Err(err) = self + .proposal_manager + .try_start_submission(current_status.is_preconfer()) + .await + { + if let Some(transaction_error) = err.downcast_ref::() { + self.handle_transaction_error(transaction_error, ¤t_status, &l2_slot_info) + .await?; + } else { + return Err(err); + } + } + + // Cleanup + if !current_status.is_submitter() + && !current_status.is_preconfer() + && self.proposal_manager.has_batches() + { + error!( + "Resetting batch builder. has batches: {}", + self.proposal_manager.has_batches(), + ); + self.proposal_manager.reset_builder().await?; + } + + Ok(()) + } + + async fn recover_from_failed_submission(&mut self) -> Result<(), Error> { + self.proposal_manager.reorg_unproposed_blocks().await?; + self.proposal_manager.reset_builder().await?; + + let l2_slot_info = self.taiko.get_l2_slot_info().await?; + self.head_verifier + .set(l2_slot_info.parent_id(), *l2_slot_info.parent_hash()) + .await; + + info!("Recovery complete. Resuming preconfirmation loop."); + Ok(()) + } + + async fn handle_transaction_error( + &mut self, + error: &TransactionError, + _current_status: &OperatorStatus, + _l2_slot_info: &L2SlotInfoV2, + ) -> Result<(), Error> { + match error { + TransactionError::ReanchorRequired => { + warn!("Unexpected ReanchorRequired error received"); + self.cancel_token.cancel_on_critical_error(); + Err(anyhow::anyhow!( + "ReanchorRequired error received unexpectedly, exiting" + )) + } + TransactionError::NotConfirmed => { + self.cancel_token.cancel_on_critical_error(); + Err(anyhow::anyhow!( + "Transaction not confirmed for a long time, exiting" + )) + } + TransactionError::UnsupportedTransactionType => { + self.cancel_token.cancel_on_critical_error(); + Err(anyhow::anyhow!("Unsupported transaction type")) + } + TransactionError::GetBlockNumberFailed => { + self.cancel_token.cancel_on_critical_error(); + Err(anyhow::anyhow!("Failed to get block number from L1")) + } + TransactionError::EstimationTooEarly => { + warn!("Transaction estimation too early"); + Ok(()) + } + TransactionError::InsufficientFunds => { + self.cancel_token.cancel_on_critical_error(); + Err(anyhow::anyhow!( + "Transaction reverted with InsufficientFunds error" + )) + } + TransactionError::EstimationFailed => { + warn!("L1 transaction estimation failed. Reorging preconfirmed L2 blocks."); + self.recover_from_failed_submission().await + } + TransactionError::TransactionReverted => { + warn!("L1 transaction reverted. Reorging preconfirmed L2 blocks."); + self.recover_from_failed_submission().await + } + TransactionError::OldestForcedInclusionDue => { + // No forced inclusions in RealTime, but handle gracefully + warn!("OldestForcedInclusionDue received in RealTime mode, ignoring"); + Ok(()) + } + TransactionError::NotTheOperatorInCurrentEpoch => { + warn!("Propose batch transaction executed too late."); + Ok(()) + } + TransactionError::BuildFailed => { + self.cancel_token.cancel_on_critical_error(); + Err(anyhow::anyhow!("Transaction build failed, exiting")) + } + } + } + + async fn get_slot_info_and_status( + &mut self, + ) -> Result<(L2SlotInfoV2, OperatorStatus, Option), Error> { + let l2_slot_info = self.taiko.get_l2_slot_info().await; + let current_status = match &l2_slot_info { + Ok(info) => self.operator.get_status(info).await, + Err(_) => Err(anyhow::anyhow!("Failed to get L2 slot info")), + }; + + let gas_limit_without_anchor = match &l2_slot_info { + Ok(info) => info.parent_gas_limit_without_anchor(), + Err(_) => { + error!("Failed to get L2 slot info set gas_limit_without_anchor to 0"); + 0u64 + } + }; + + let pending_tx_list = if gas_limit_without_anchor != 0 { + let batches_ready_to_send = 0; + match &l2_slot_info { + Ok(info) => { + self.taiko + .get_pending_l2_tx_list_from_l2_engine( + info.base_fee(), + batches_ready_to_send, + gas_limit_without_anchor, + ) + .await + } + Err(_) => Err(anyhow::anyhow!("Failed to get L2 slot info")), + } + } else { + Ok(None) + }; + + self.print_current_slots_info( + ¤t_status, + &pending_tx_list, + &l2_slot_info, + self.proposal_manager.get_number_of_batches(), + )?; + + Ok((l2_slot_info?, current_status?, pending_tx_list?)) + } + + async fn verify_preconfed_block( + &self, + l2_block: BuildPreconfBlockResponse, + ) -> Result<(), Error> { + if !self + .head_verifier + .verify_next_and_set(l2_block.number, l2_block.hash, l2_block.parent_hash) + .await + { + self.head_verifier.log_error().await; + self.cancel_token.cancel_on_critical_error(); + return Err(anyhow::anyhow!( + "Unexpected L2 head after preconfirmation. Restarting node..." + )); + } + Ok(()) + } + + fn print_current_slots_info( + &self, + current_status: &Result, + pending_tx_list: &Result, Error>, + l2_slot_info: &Result, + batches_number: u64, + ) -> Result<(), Error> { + let l1_slot = self.ethereum_l1.slot_clock.get_current_slot()?; + info!(target: "heartbeat", + "| Epoch: {:<6} | Slot: {:<2} | L2 Slot: {:<2} | {}{} Batches: {batches_number} | {} |", + self.ethereum_l1.slot_clock.get_epoch_from_slot(l1_slot), + self.ethereum_l1.slot_clock.slot_of_epoch(l1_slot), + self.ethereum_l1 + .slot_clock + .get_current_l2_slot_within_l1_slot()?, + if let Ok(pending_tx_list) = pending_tx_list { + format!( + "Txs: {:<4} |", + pending_tx_list + .as_ref() + .map_or(0, |tx_list| tx_list.tx_list.len()) + ) + } else { + "Txs: unknown |".to_string() + }, + if let Ok(l2_slot_info) = l2_slot_info { + format!( + " Fee: {:<7} | L2: {:<6} | Time: {:<10} | Hash: {} |", + l2_slot_info.base_fee(), + l2_slot_info.parent_id(), + l2_slot_info.slot_timestamp(), + &l2_slot_info.parent_hash().to_string()[..8] + ) + } else { + " L2 slot info unknown |".to_string() + }, + if let Ok(status) = current_status { + status.to_string() + } else { + "Unknown".to_string() + }, + ); + Ok(()) + } + + async fn warmup(&mut self) -> Result<(), Error> { + info!("Warmup RealTime node"); + + // Wait for RealTimeInbox activation (lastFinalizedBlockHash != 0) + loop { + let hash = self + .ethereum_l1 + .execution_layer + .get_last_finalized_block_hash() + .await?; + if hash != alloy::primitives::B256::ZERO { + info!("RealTimeInbox is active, lastFinalizedBlockHash: {}", hash); + break; + } + warn!("RealTimeInbox not yet activated. Waiting..."); + sleep(Duration::from_secs(12)).await; + } + + // Wait for the last sent transaction to be executed + self.wait_for_sent_transactions().await?; + + // Reorg any preconfirmed-but-unproposed L2 blocks back to the last proposed block + if !self.preconf_only { + self.proposal_manager.reorg_unproposed_blocks().await?; + } + + Ok(()) + } + + async fn wait_for_sent_transactions(&self) -> Result<(), Error> { + loop { + let nonce_latest: u64 = self + .ethereum_l1 + .execution_layer + .get_preconfer_nonce_latest() + .await?; + let nonce_pending: u64 = self + .ethereum_l1 + .execution_layer + .get_preconfer_nonce_pending() + .await?; + if nonce_pending == nonce_latest { + break; + } + debug!( + "Waiting for sent transactions to be executed. Nonce Latest: {nonce_latest}, Nonce Pending: {nonce_pending}" + ); + sleep(Duration::from_secs(6)).await; + } + + Ok(()) + } +} diff --git a/realtime/src/node/proposal_manager/async_submitter.rs b/realtime/src/node/proposal_manager/async_submitter.rs new file mode 100644 index 00000000..ba732622 --- /dev/null +++ b/realtime/src/node/proposal_manager/async_submitter.rs @@ -0,0 +1,407 @@ +use crate::l1::execution_layer::ExecutionLayer; +use crate::node::proposal_manager::bridge_handler::{UserOpStatus, UserOpStatusStore}; +use crate::node::proposal_manager::proposal::Proposal; +use crate::raiko::{ + RaikoBlobSlice, RaikoCheckpoint, RaikoClient, RaikoDerivationSource, RaikoProofRequest, +}; +use alloy::consensus::SidecarBuilder; +use alloy::primitives::B256; +use anyhow::Error; +use common::l1::ethereum_l1::EthereumL1; +use std::sync::Arc; +use taiko_protocol::shasta::BlobCoder; +use taiko_protocol::shasta::manifest::{BlockManifest, DerivationSourceManifest}; +use tokio::sync::oneshot; +use tokio::task::JoinHandle; +use tracing::info; + +pub struct SubmissionResult { + pub new_last_finalized_block_hash: B256, + pub new_last_finalized_block_number: u64, +} + +struct InFlightSubmission { + result_rx: oneshot::Receiver>, + handle: JoinHandle<()>, +} + +pub struct AsyncSubmitter { + in_flight: Option, + raiko_client: RaikoClient, + basefee_sharing_pctg: u8, + ethereum_l1: Arc>, + proof_request_bypass: bool, +} + +impl AsyncSubmitter { + pub fn new( + raiko_client: RaikoClient, + basefee_sharing_pctg: u8, + ethereum_l1: Arc>, + proof_request_bypass: bool, + ) -> Self { + Self { + in_flight: None, + raiko_client, + basefee_sharing_pctg, + ethereum_l1, + proof_request_bypass, + } + } + + pub fn is_busy(&self) -> bool { + self.in_flight.is_some() + } + + /// Non-blocking check for completed submission. Returns None if idle or still in progress. + pub fn try_recv_result(&mut self) -> Option> { + let in_flight = self.in_flight.as_mut()?; + match in_flight.result_rx.try_recv() { + Ok(result) => { + self.in_flight = None; + Some(result) + } + Err(oneshot::error::TryRecvError::Empty) => None, + Err(oneshot::error::TryRecvError::Closed) => { + self.in_flight = None; + Some(Err(anyhow::anyhow!( + "Submission task panicked or was dropped" + ))) + } + } + } + + /// Submit a proposal asynchronously. Spawns a background task that fetches the ZK proof + /// from Raiko and then sends the L1 transaction. Results are retrieved via `try_recv_result`. + pub fn submit(&mut self, proposal: Proposal, status_store: Option) { + assert!( + !self.is_busy(), + "Cannot submit while another submission is in flight" + ); + + let (result_tx, result_rx) = oneshot::channel(); + let raiko_client = self.raiko_client.clone(); + let basefee_sharing_pctg = self.basefee_sharing_pctg; + let ethereum_l1 = self.ethereum_l1.clone(); + let proof_request_bypass = self.proof_request_bypass; + + let handle = tokio::spawn(async move { + let result = submission_task( + proposal, + &raiko_client, + basefee_sharing_pctg, + ethereum_l1, + status_store, + proof_request_bypass, + ) + .await; + let _ = result_tx.send(result); + }); + + self.in_flight = Some(InFlightSubmission { result_rx, handle }); + } + + pub fn abort(&mut self) { + if let Some(in_flight) = self.in_flight.take() { + in_flight.handle.abort(); + } + } +} + +async fn submission_task( + mut proposal: Proposal, + raiko_client: &RaikoClient, + basefee_sharing_pctg: u8, + ethereum_l1: Arc>, + status_store: Option, + proof_request_bypass: bool, +) -> Result { + // Step 1: Fetch ZK proof from Raiko (or bypass) + if proposal.zk_proof.is_none() { + let l2_block_numbers: Vec = + (proposal.checkpoint.blockNumber.to::() - u64::try_from(proposal.l2_blocks.len())? + + 1..=proposal.checkpoint.blockNumber.to::()) + .collect(); + + // Build the blob sidecar (same as proposal_tx_builder) to get blob hashes and raw data + let mut block_manifests = Vec::with_capacity(proposal.l2_blocks.len()); + for l2_block in &proposal.l2_blocks { + block_manifests.push(BlockManifest { + timestamp: l2_block.timestamp_sec, + coinbase: l2_block.coinbase, + anchor_block_number: l2_block.anchor_block_number, + gas_limit: l2_block.gas_limit_without_anchor, + transactions: l2_block + .prebuilt_tx_list + .tx_list + .iter() + .map(|tx| tx.clone().into()) + .collect(), + }); + } + let manifest = DerivationSourceManifest { + blocks: block_manifests, + }; + let manifest_data = manifest.encode_and_compress()?; + let sidecar_builder: SidecarBuilder = SidecarBuilder::from_slice(&manifest_data); + let sidecar: alloy::eips::eip4844::BlobTransactionSidecar = sidecar_builder.build()?; + + // Extract versioned blob hashes + let blob_hashes: Vec = sidecar + .versioned_hashes() + .map(|h| format!("0x{}", hex::encode(h))) + .collect(); + + // Extract raw blob data (each blob is 131072 bytes, hex-encoded with 0x prefix) + let blobs: Vec = sidecar + .blobs + .iter() + .map(|blob| format!("0x{}", hex::encode::<&[u8]>(blob.as_ref()))) + .collect(); + + // Build sources array with a single DerivationSource entry + let sources = vec![RaikoDerivationSource { + is_forced_inclusion: false, + blob_slice: RaikoBlobSlice { + blob_hashes, + offset: 0, + timestamp: 0, + }, + }]; + + let request = RaikoProofRequest { + l2_block_numbers, + proof_type: raiko_client.proof_type.raiko_proof_type().to_string(), + max_anchor_block_number: proposal.max_anchor_block_number, + last_finalized_block_hash: format!( + "0x{}", + hex::encode(proposal.last_finalized_block_hash) + ), + basefee_sharing_pctg, + network: None, + l1_network: None, + prover: None, + signal_slots: proposal + .signal_slots + .iter() + .map(|s| format!("0x{}", hex::encode(s))) + .collect(), + sources, + blobs, + checkpoint: Some(RaikoCheckpoint { + block_number: proposal.checkpoint.blockNumber.to::(), + block_hash: format!("0x{}", hex::encode(proposal.checkpoint.blockHash)), + state_root: format!("0x{}", hex::encode(proposal.checkpoint.stateRoot)), + }), + blob_proof_type: "proof_of_equivalence".to_string(), + }; + + if proof_request_bypass { + let json = serde_json::to_string_pretty(&request)?; + let raiko_url = format!("{}/v3/proof/batch/realtime", raiko_client.base_url); + + std::fs::write("/tmp/raiko_request.json", &json)?; + + let api_key_header = raiko_client + .api_key + .as_ref() + .map(|k| format!(" -H 'X-API-KEY: {}' \\\n", k)) + .unwrap_or_default(); + let curl_script = format!( + "#!/bin/bash\n\ + # Generated by Catalyst — send this to your Raiko instance\n\ + # Usage: RAIKO_URL=http://your-raiko:8080 bash /tmp/raiko_curl.sh\n\n\ + RAIKO_URL=\"${{RAIKO_URL:-{raiko_url}}}\"\n\n\ + curl -X POST \"$RAIKO_URL\" \\\n\ + {api_key_header}\ + \x20 -H 'Content-Type: application/json' \\\n\ + \x20 -d @/tmp/raiko_request.json\n" + ); + std::fs::write("/tmp/raiko_curl.sh", &curl_script)?; + + info!( + "PROOF_REQUEST_BYPASS: Raiko request dumped.\n\ + Request JSON: /tmp/raiko_request.json\n\ + Curl script: /tmp/raiko_curl.sh\n\ + Raiko URL: {}\n\ + Skipping Raiko call and L1 submission.", + raiko_url + ); + + return Ok(SubmissionResult { + new_last_finalized_block_hash: proposal.checkpoint.blockHash, + new_last_finalized_block_number: proposal.checkpoint.blockNumber.to::(), + }); + } + + // Set user op status to ProvingBlock before requesting proof from Raiko + if let Some(ref store) = status_store { + for op in &proposal.user_ops { + store.set( + op.id, + &UserOpStatus::ProvingBlock { + block_id: proposal.checkpoint.blockNumber.to::(), + }, + ); + } + // Also track L2 direct UserOps + for id in &proposal.l2_user_op_ids { + store.set( + *id, + &UserOpStatus::ProvingBlock { + block_id: proposal.checkpoint.blockNumber.to::(), + }, + ); + } + } + + let proof = match raiko_client.get_proof(&request).await { + Ok(proof) => proof, + Err(e) => { + if let Some(ref store) = status_store { + let reason = format!("Proof generation failed: {}", e); + for op in &proposal.user_ops { + store.set( + op.id, + &UserOpStatus::Rejected { + reason: reason.clone(), + }, + ); + } + for id in &proposal.l2_user_op_ids { + store.set( + *id, + &UserOpStatus::Rejected { + reason: reason.clone(), + }, + ); + } + } + return Err(e); + } + }; + proposal.zk_proof = Some(proof); + } + + // Step 2: Send L1 transaction + let mut user_op_ids: Vec = proposal.user_ops.iter().map(|op| op.id).collect(); + user_op_ids.extend(&proposal.l2_user_op_ids); + let has_user_ops = !user_op_ids.is_empty() && status_store.is_some(); + + let (tx_hash_sender, tx_hash_receiver) = if has_user_ops { + let (s, r) = tokio::sync::oneshot::channel(); + (Some(s), Some(r)) + } else { + (None, None) + }; + let (tx_result_sender, tx_result_receiver) = if has_user_ops { + let (s, r) = tokio::sync::oneshot::channel(); + (Some(s), Some(r)) + } else { + (None, None) + }; + + if let Err(err) = ethereum_l1 + .execution_layer + .send_batch_to_l1(proposal.clone(), tx_hash_sender, tx_result_sender) + .await + { + // Mark all user ops (L1 and L2) as rejected on failure + if let Some(ref store) = status_store { + let reason = format!("L1 multicall failed: {}", err); + for op in &proposal.user_ops { + store.set( + op.id, + &UserOpStatus::Rejected { + reason: reason.clone(), + }, + ); + } + for id in &proposal.l2_user_op_ids { + store.set( + *id, + &UserOpStatus::Rejected { + reason: reason.clone(), + }, + ); + } + } + return Err(err); + } + + // Step 3: After successful submission, the new lastFinalizedBlockHash is the checkpoint's blockHash + let new_last_finalized_block_hash = proposal.checkpoint.blockHash; + let new_last_finalized_block_number = proposal.checkpoint.blockNumber.to::(); + + // Step 4: Spawn user-op status tracker + if let (Some(hash_rx), Some(result_rx), Some(store)) = + (tx_hash_receiver, tx_result_receiver, status_store) + { + tokio::spawn(async move { + let tx_hash = match hash_rx.await { + Ok(tx_hash) => { + for id in &user_op_ids { + store.set(*id, &UserOpStatus::Processing { tx_hash }); + } + Some(tx_hash) + } + Err(_) => { + for id in &user_op_ids { + store.set( + *id, + &UserOpStatus::Rejected { + reason: "Transaction failed to send".to_string(), + }, + ); + } + None + } + }; + + if tx_hash.is_some() { + match result_rx.await { + Ok(true) => { + for id in &user_op_ids { + store.set(*id, &UserOpStatus::Executed); + } + } + Ok(false) => { + for id in &user_op_ids { + store.set( + *id, + &UserOpStatus::Rejected { + reason: "L1 multicall reverted".to_string(), + }, + ); + } + } + Err(_) => { + for id in &user_op_ids { + store.set( + *id, + &UserOpStatus::Rejected { + reason: "Transaction monitor dropped".to_string(), + }, + ); + } + } + } + } + + // Clean up status entries after 60s (client should have polled by then) + let cleanup_store = store.clone(); + let cleanup_ids = user_op_ids.clone(); + tokio::spawn(async move { + tokio::time::sleep(tokio::time::Duration::from_secs(60)).await; + for id in &cleanup_ids { + cleanup_store.remove(*id); + } + }); + }); + } + + Ok(SubmissionResult { + new_last_finalized_block_hash, + new_last_finalized_block_number, + }) +} diff --git a/realtime/src/node/proposal_manager/batch_builder.rs b/realtime/src/node/proposal_manager/batch_builder.rs new file mode 100644 index 00000000..90d7cf74 --- /dev/null +++ b/realtime/src/node/proposal_manager/batch_builder.rs @@ -0,0 +1,349 @@ +use crate::l1::bindings::ICheckpointStore::Checkpoint; +use crate::node::proposal_manager::{ + bridge_handler::{L1Call, UserOp}, + l2_block_payload::L2BlockV2Payload, + proposal::Proposal, +}; +use alloy::primitives::{B256, FixedBytes}; +use anyhow::Error; +use common::metrics::Metrics; +use common::{ + batch_builder::BatchBuilderConfig, + shared::l2_block_v2::{L2BlockV2, L2BlockV2Draft}, +}; +use common::{l1::slot_clock::SlotClock, shared::anchor_block_info::AnchorBlockInfo}; +use std::{collections::VecDeque, sync::Arc}; +use tracing::{debug, info, trace, warn}; + +pub struct BatchBuilder { + config: BatchBuilderConfig, + proposals_to_send: VecDeque, + current_proposal: Option, + slot_clock: Arc, + #[allow(dead_code)] + metrics: Arc, +} + +impl BatchBuilder { + pub fn new( + config: BatchBuilderConfig, + slot_clock: Arc, + metrics: Arc, + ) -> Self { + Self { + config, + proposals_to_send: VecDeque::new(), + current_proposal: None, + slot_clock, + metrics, + } + } + + pub fn get_config(&self) -> &BatchBuilderConfig { + &self.config + } + + pub fn can_consume_l2_block(&mut self, l2_draft_block: &L2BlockV2Draft) -> bool { + let is_time_shift_expired = self.is_time_shift_expired(l2_draft_block.timestamp_sec); + self.current_proposal.as_mut().is_some_and(|batch| { + let new_block_count = match u16::try_from(batch.l2_blocks.len() + 1) { + Ok(n) => n, + Err(_) => return false, + }; + + let mut new_total_bytes = + batch.total_bytes + l2_draft_block.prebuilt_tx_list.bytes_length; + + if !self.config.is_within_bytes_limit(new_total_bytes) { + batch.compress(); + new_total_bytes = batch.total_bytes + l2_draft_block.prebuilt_tx_list.bytes_length; + if !self.config.is_within_bytes_limit(new_total_bytes) { + let start = std::time::Instant::now(); + let mut batch_clone = batch.clone(); + batch_clone.add_l2_draft_block(l2_draft_block.clone()); + batch_clone.compress(); + new_total_bytes = batch_clone.total_bytes; + debug!( + "can_consume_l2_block: Second compression took {} ms, new total bytes: {}", + start.elapsed().as_millis(), + new_total_bytes + ); + } + } + + self.config.is_within_bytes_limit(new_total_bytes) + && self.config.is_within_block_limit(new_block_count) + && !is_time_shift_expired + }) + } + + pub fn create_new_batch( + &mut self, + anchor_block: AnchorBlockInfo, + last_finalized_block_hash: B256, + ) { + self.finalize_current_batch(); + + self.current_proposal = Some(Proposal { + l2_blocks: vec![], + total_bytes: 0, + coinbase: self.config.default_coinbase, + max_anchor_block_number: anchor_block.id(), + max_anchor_block_hash: anchor_block.hash(), + max_anchor_state_root: anchor_block.state_root(), + checkpoint: Checkpoint::default(), + last_finalized_block_hash, + user_ops: vec![], + l2_user_op_ids: vec![], + signal_slots: vec![], + l1_calls: vec![], + zk_proof: None, + }); + } + + pub fn add_l2_draft_block( + &mut self, + l2_draft_block: L2BlockV2Draft, + ) -> Result { + if let Some(current_proposal) = self.current_proposal.as_mut() { + let payload = current_proposal.add_l2_draft_block(l2_draft_block); + + debug!( + "Added L2 draft block to batch: l2 blocks: {}, total bytes: {}", + current_proposal.l2_blocks.len(), + current_proposal.total_bytes + ); + Ok(payload) + } else { + Err(anyhow::anyhow!("No current batch")) + } + } + + /// Add a pre-built L2BlockV2 directly to the current proposal. + /// Used during recovery to bypass the draft/payload flow. + #[allow(dead_code)] + pub fn add_recovered_l2_block(&mut self, l2_block: L2BlockV2) -> Result<(), Error> { + if let Some(current_proposal) = self.current_proposal.as_mut() { + current_proposal.total_bytes += l2_block.prebuilt_tx_list.bytes_length; + current_proposal.l2_blocks.push(l2_block); + Ok(()) + } else { + Err(anyhow::anyhow!("No current batch for recovered block")) + } + } + + pub fn add_user_op(&mut self, user_op_data: UserOp) -> Result<&Proposal, Error> { + if let Some(current_proposal) = self.current_proposal.as_mut() { + current_proposal.user_ops.push(user_op_data.clone()); + info!("Added user op: {:?}", user_op_data); + Ok(current_proposal) + } else { + Err(anyhow::anyhow!("No current batch")) + } + } + + pub fn add_l2_user_op_id(&mut self, id: u64) -> Result<(), Error> { + if let Some(current_proposal) = self.current_proposal.as_mut() { + current_proposal.l2_user_op_ids.push(id); + Ok(()) + } else { + Err(anyhow::anyhow!("No current batch for L2 user op id")) + } + } + + pub fn add_signal_slot(&mut self, signal_slot: FixedBytes<32>) -> Result<&Proposal, Error> { + if let Some(current_proposal) = self.current_proposal.as_mut() { + current_proposal.signal_slots.push(signal_slot); + info!("Added signal slot: {:?}", signal_slot); + Ok(current_proposal) + } else { + Err(anyhow::anyhow!("No current batch")) + } + } + + pub fn add_l1_call(&mut self, l1_call: L1Call) -> Result<&Proposal, Error> { + if let Some(current_proposal) = self.current_proposal.as_mut() { + current_proposal.l1_calls.push(l1_call.clone()); + info!("Added L1 call: {:?}", l1_call); + Ok(current_proposal) + } else { + Err(anyhow::anyhow!("No current batch")) + } + } + + pub fn set_proposal_checkpoint(&mut self, checkpoint: Checkpoint) -> Result<&Proposal, Error> { + if let Some(current_proposal) = self.current_proposal.as_mut() { + current_proposal.checkpoint = checkpoint.clone(); + debug!("Update proposal checkpoint: {:?}", checkpoint); + Ok(current_proposal) + } else { + Err(anyhow::anyhow!("No current batch")) + } + } + + pub fn get_current_proposal_last_block_timestamp(&self) -> Option { + self.current_proposal + .as_ref() + .and_then(|p| p.l2_blocks.last().map(|b| b.timestamp_sec)) + } + + pub fn remove_last_l2_block(&mut self) { + if let Some(current_proposal) = self.current_proposal.as_mut() { + let removed_block = current_proposal.l2_blocks.pop(); + if let Some(removed_block) = removed_block { + current_proposal.total_bytes -= removed_block.prebuilt_tx_list.bytes_length; + if current_proposal.l2_blocks.is_empty() { + self.current_proposal = None; + } + debug!( + "Removed L2 block from batch: {} txs, {} bytes", + removed_block.prebuilt_tx_list.tx_list.len(), + removed_block.prebuilt_tx_list.bytes_length + ); + } + } + } + + pub fn is_empty(&self) -> bool { + trace!( + "batch_builder::is_empty: current_proposal is none: {}, proposals_to_send len: {}", + self.current_proposal.is_none(), + self.proposals_to_send.len() + ); + self.current_proposal.is_none() && self.proposals_to_send.is_empty() + } + + /// Finalize the current batch if appropriate for submission. + pub fn finalize_if_needed(&mut self, submit_only_full_batches: bool) { + if self.current_proposal.is_some() + && (!submit_only_full_batches + || !self.config.is_within_block_limit( + u16::try_from( + self.current_proposal + .as_ref() + .map(|b| b.l2_blocks.len()) + .unwrap_or(0), + ) + .unwrap_or(u16::MAX) + + 1, + )) + { + self.finalize_current_batch(); + } + } + + /// Pop the oldest finalized batch, stamping it with the current last_finalized_block_hash. + pub fn pop_oldest_batch(&mut self, last_finalized_block_hash: B256) -> Option { + if let Some(mut batch) = self.proposals_to_send.pop_front() { + batch.last_finalized_block_hash = last_finalized_block_hash; + Some(batch) + } else { + None + } + } + + /// Re-queue a batch at the front (e.g., when submission couldn't start). + pub fn push_front_batch(&mut self, batch: Proposal) { + self.proposals_to_send.push_front(batch); + } + + pub fn is_time_shift_expired(&self, current_l2_slot_timestamp: u64) -> bool { + if let Some(current_proposal) = self.current_proposal.as_ref() + && let Some(last_block) = current_proposal.l2_blocks.last() + { + return current_l2_slot_timestamp - last_block.timestamp_sec + > self.config.max_time_shift_between_blocks_sec; + } + false + } + + pub fn is_time_shift_between_blocks_expiring(&self, current_l2_slot_timestamp: u64) -> bool { + if let Some(current_proposal) = self.current_proposal.as_ref() + && let Some(last_block) = current_proposal.l2_blocks.last() + { + if current_l2_slot_timestamp < last_block.timestamp_sec { + warn!("Preconfirmation timestamp is before the last block timestamp"); + return false; + } + return self.is_the_last_l1_slot_to_add_an_empty_l2_block( + current_l2_slot_timestamp, + last_block.timestamp_sec, + ); + } + false + } + + fn is_the_last_l1_slot_to_add_an_empty_l2_block( + &self, + current_l2_slot_timestamp: u64, + last_block_timestamp: u64, + ) -> bool { + current_l2_slot_timestamp - last_block_timestamp + >= self.config.max_time_shift_between_blocks_sec - self.config.l1_slot_duration_sec + } + + pub fn is_greater_than_max_anchor_height_offset(&self) -> Result { + if let Some(current_proposal) = self.current_proposal.as_ref() { + let current_l1_block = self.slot_clock.get_current_slot()?; + if current_l1_block > current_proposal.max_anchor_block_number { + let offset = current_l1_block - current_proposal.max_anchor_block_number; + return Ok(offset > self.config.max_anchor_height_offset); + } + } + Ok(false) + } + + fn is_empty_block_required(&self, preconfirmation_timestamp: u64) -> bool { + self.is_time_shift_between_blocks_expiring(preconfirmation_timestamp) + } + + pub fn get_number_of_batches(&self) -> u64 { + self.proposals_to_send.len() as u64 + + if self.current_proposal.is_some() { + 1 + } else { + 0 + } + } + + pub fn finalize_current_batch(&mut self) { + if let Some(batch) = self.current_proposal.take() + && !batch.l2_blocks.is_empty() + { + self.proposals_to_send.push_back(batch); + } + } + + pub fn should_new_block_be_created( + &self, + pending_tx_list: &Option, + current_l2_slot_timestamp: u64, + end_of_sequencing: bool, + ) -> bool { + let number_of_pending_txs = pending_tx_list + .as_ref() + .map(|tx_list| tx_list.tx_list.len()) + .unwrap_or(0) as u64; + + if self.is_empty_block_required(current_l2_slot_timestamp) || end_of_sequencing { + return true; + } + + if number_of_pending_txs >= self.config.preconf_min_txs { + return true; + } + + if let Some(current_proposal) = self.current_proposal.as_ref() + && let Some(last_block) = current_proposal.l2_blocks.last() + { + let number_of_l2_slots = + (current_l2_slot_timestamp.saturating_sub(last_block.timestamp_sec)) * 1000 + / self.slot_clock.get_preconf_heartbeat_ms(); + return number_of_l2_slots > self.config.preconf_max_skipped_l2_slots; + } + + true + } +} + +use common::shared::l2_tx_lists::PreBuiltTxList; diff --git a/realtime/src/node/proposal_manager/bridge_handler.rs b/realtime/src/node/proposal_manager/bridge_handler.rs new file mode 100644 index 00000000..0d981e5c --- /dev/null +++ b/realtime/src/node/proposal_manager/bridge_handler.rs @@ -0,0 +1,374 @@ +use crate::l2::taiko::Taiko; +use crate::shared_abi::bindings::IBridge::Message; +use crate::{ + l1::execution_layer::{ExecutionLayer, L1BridgeHandlerOps}, + l2::execution_layer::L2BridgeHandlerOps, +}; +use alloy::primitives::{Address, B256, Bytes, FixedBytes}; +use anyhow::Result; +use common::{l1::ethereum_l1::EthereumL1, utils::cancellation_token::CancellationToken}; +use jsonrpsee::server::{RpcModule, ServerBuilder}; +use serde::{Deserialize, Serialize}; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::{net::SocketAddr, sync::Arc}; +use tokio::sync::mpsc::{self, Receiver}; +use tracing::{debug, error, info, warn}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "status")] +pub enum UserOpStatus { + Pending, + Processing { tx_hash: FixedBytes<32> }, + ProvingBlock { block_id: u64 }, + Rejected { reason: String }, + Executed, +} + +/// Disk-backed user op status store using sled. +#[derive(Clone)] +pub struct UserOpStatusStore { + db: sled::Db, +} + +impl UserOpStatusStore { + pub fn open(path: &str) -> Result { + let db = sled::open(path) + .map_err(|e| anyhow::anyhow!("Failed to open user op status store: {}", e))?; + Ok(Self { db }) + } + + pub fn set(&self, id: u64, status: &UserOpStatus) { + if let Ok(value) = serde_json::to_vec(status) + && let Err(e) = self.db.insert(id.to_be_bytes(), value) + { + error!("Failed to write user op status: {}", e); + } + } + + pub fn get(&self, id: u64) -> Option { + self.db + .get(id.to_be_bytes()) + .ok() + .flatten() + .and_then(|v| serde_json::from_slice(&v).ok()) + } + + pub fn remove(&self, id: u64) { + let _ = self.db.remove(id.to_be_bytes()); + } +} + +#[derive(Debug, Clone, Deserialize)] +pub struct UserOp { + #[serde(default)] + pub id: u64, + pub submitter: Address, + pub calldata: Bytes, + #[serde(default, rename = "chainId")] + pub chain_id: u64, +} + +// Data required to build the L1 call transaction initiated by an L2 contract via the bridge +#[derive(Clone, Debug)] +pub struct L1Call { + pub message_from_l2: Message, + pub signal_slot_proof: Bytes, +} + +// Data required to build the L2 call transaction initiated by an L1 contract via the bridge +#[derive(Clone, Debug)] +pub struct L2Call { + pub message_from_l1: Message, + pub signal_slot_on_l2: FixedBytes<32>, +} + +/// Result of routing a UserOp: either it targets L1 (and triggers an L2 bridge call) +/// or it targets L2 (for direct execution on L2, e.g. bridge-out). +#[allow(clippy::large_enum_variant)] +pub enum UserOpRouting { + /// L1 UserOp that triggers a bridge deposit (L1→L2). + L1ToL2 { user_op: UserOp, l2_call: L2Call }, + /// L2 UserOp for direct execution on L2 (e.g. bridge-out L2→L1). + L2Direct { user_op: UserOp }, +} + +#[derive(Debug, Deserialize)] +struct TxStatusRequest { + #[serde(default, rename = "userOpId")] + user_op_id: Option, + #[serde(default, rename = "txHash")] + tx_hash: Option, +} + +#[derive(Clone)] +struct BridgeRpcContext { + tx: mpsc::Sender, + status_store: UserOpStatusStore, + next_id: Arc, + taiko: Arc, + last_finalized_block_number: Arc, +} + +pub struct BridgeHandler { + ethereum_l1: Arc>, + taiko: Arc, + rx: Receiver, + status_store: UserOpStatusStore, + l1_chain_id: u64, + l2_chain_id: u64, +} + +impl BridgeHandler { + pub async fn new( + addr: SocketAddr, + ethereum_l1: Arc>, + taiko: Arc, + cancellation_token: CancellationToken, + l1_chain_id: u64, + l2_chain_id: u64, + last_finalized_block_number: Arc, + ) -> Result { + let (tx, rx) = mpsc::channel::(1024); + let status_store = UserOpStatusStore::open("data/user_op_status")?; + + let rpc_context = BridgeRpcContext { + tx, + status_store: status_store.clone(), + next_id: Arc::new(AtomicU64::new(1)), + taiko: taiko.clone(), + last_finalized_block_number, + }; + + let server = ServerBuilder::default() + .build(addr) + .await + .map_err(|e| anyhow::anyhow!("Failed to build RPC server: {}", e))?; + + let mut module = RpcModule::new(rpc_context); + + module.register_async_method("surge_sendUserOp", |params, ctx, _| async move { + let mut user_op: UserOp = params.parse()?; + let id = ctx.next_id.fetch_add(1, Ordering::Relaxed); + user_op.id = id; + + info!( + "Received UserOp: id={}, submitter={:?}, calldata_len={}", + id, + user_op.submitter, + user_op.calldata.len() + ); + + ctx.status_store.set(id, &UserOpStatus::Pending); + + ctx.tx.send(user_op).await.map_err(|e| { + error!("Failed to send UserOp to queue: {}", e); + ctx.status_store.remove(id); + jsonrpsee::types::ErrorObjectOwned::owned( + -32000, + "Failed to queue user operation", + Some(format!("{}", e)), + ) + })?; + + Ok::(id) + })?; + + module.register_async_method("surge_userOpStatus", |params, ctx, _| async move { + let id: u64 = params.one()?; + + match ctx.status_store.get(id) { + Some(status) => Ok::( + serde_json::to_value(status).map_err(|e| { + jsonrpsee::types::ErrorObjectOwned::owned( + -32603, + "Serialization error", + Some(format!("{}", e)), + ) + })?, + ), + None => Err(jsonrpsee::types::ErrorObjectOwned::owned( + -32001, + "UserOp not found", + Some(format!("No user operation with id {}", id)), + )), + } + })?; + + module.register_async_method("surge_txStatus", |params, ctx, _| async move { + let request: TxStatusRequest = params.parse()?; + + match (request.user_op_id, request.tx_hash) { + (Some(id), None) => { + // Existing userOpId lookup via status store + match ctx.status_store.get(id) { + Some(status) => serde_json::to_value(status).map_err(|e| { + jsonrpsee::types::ErrorObjectOwned::owned( + -32603, + "Serialization error", + Some(format!("{}", e)), + ) + }), + None => Err(jsonrpsee::types::ErrorObjectOwned::owned( + -32001, + "UserOp not found", + Some(format!("No user operation with id {}", id)), + )), + } + } + (None, Some(hash)) => { + // Look up L2 transaction by hash + let tx = ctx.taiko.get_transaction_by_hash(hash).await.map_err(|e| { + debug!("Transaction {} not found on L2: {}", hash, e); + jsonrpsee::types::ErrorObjectOwned::owned( + -32001, + "Transaction not found", + Some(format!("L2 transaction {} not found: {}", hash, e)), + ) + })?; + + let block_number = tx.block_number.ok_or_else(|| { + jsonrpsee::types::ErrorObjectOwned::owned( + -32001, + "Transaction pending", + Some("Transaction has not been included in a block yet".to_string()), + ) + })?; + + let finalized = ctx.last_finalized_block_number.load(Ordering::Relaxed); + + let status = if block_number <= finalized { + UserOpStatus::Executed + } else { + UserOpStatus::ProvingBlock { + block_id: block_number, + } + }; + + serde_json::to_value(status).map_err(|e| { + jsonrpsee::types::ErrorObjectOwned::owned( + -32603, + "Serialization error", + Some(format!("{}", e)), + ) + }) + } + _ => Err(jsonrpsee::types::ErrorObjectOwned::owned( + -32602, + "Invalid params", + Some("Provide exactly one of 'userOpId' or 'txHash'".to_string()), + )), + } + })?; + + info!("Bridge handler RPC server starting on {}", addr); + let handle = server.start(module); + + tokio::spawn(async move { + cancellation_token.cancelled().await; + info!("Cancellation token triggered, stopping bridge handler RPC server"); + handle.stop().ok(); + }); + + Ok(Self { + ethereum_l1, + taiko, + rx, + status_store, + l1_chain_id, + l2_chain_id, + }) + } + + pub fn status_store(&self) -> UserOpStatusStore { + self.status_store.clone() + } + + /// Dequeue the next UserOp and route it based on the `chainId` param. + /// + /// If `chainId` matches L1, simulates on L1 to extract bridge message (L1→L2 deposit). + /// If `chainId` matches L2, returns it for direct L2 block inclusion (bridge-out). + /// If `chainId` is 0 or missing, defaults to L1 (backwards compatible). + pub async fn next_user_op_routed(&mut self) -> Result, anyhow::Error> { + let Ok(user_op) = self.rx.try_recv() else { + return Ok(None); + }; + + if user_op.chain_id == self.l2_chain_id { + info!( + "UserOp id={} targets L2 (chainId={}), queueing for L2 execution", + user_op.id, user_op.chain_id + ); + return Ok(Some(UserOpRouting::L2Direct { user_op })); + } + + // Reject unknown chain IDs (0 is allowed as default-to-L1) + if user_op.chain_id != 0 && user_op.chain_id != self.l1_chain_id { + warn!( + "UserOp id={} has unknown chainId={}, rejecting", + user_op.id, user_op.chain_id + ); + self.status_store.set( + user_op.id, + &UserOpStatus::Rejected { + reason: format!("Unknown chainId: {}", user_op.chain_id), + }, + ); + return Ok(None); + } + + // L1 UserOp — simulate on L1 to extract bridge message + if let Some((message_from_l1, signal_slot_on_l2)) = self + .ethereum_l1 + .execution_layer + .find_message_and_signal_slot(user_op.clone()) + .await? + { + return Ok(Some(UserOpRouting::L1ToL2 { + user_op, + l2_call: L2Call { + message_from_l1, + signal_slot_on_l2, + }, + })); + } + + warn!( + "UserOp id={} targets L1 but no bridge message found", + user_op.id + ); + self.status_store.set( + user_op.id, + &UserOpStatus::Rejected { + reason: "L1 UserOp with no bridge message".to_string(), + }, + ); + Ok(None) + } + + pub async fn find_l1_call( + &mut self, + block_id: u64, + state_root: B256, + ) -> Result, anyhow::Error> { + let l2_el = self.taiko.l2_execution_layer(); + + if let Some((message_from_l2, signal_slot)) = + l2_el.find_message_and_signal_slot(block_id).await? + { + let signal_slot_proof = l2_el + .get_hop_proof(signal_slot, block_id, state_root) + .await?; + + return Ok(Some(L1Call { + message_from_l2, + signal_slot_proof, + })); + } + + Ok(None) + } + + pub fn has_pending_user_ops(&self) -> bool { + !self.rx.is_empty() + } +} diff --git a/realtime/src/node/proposal_manager/l2_block_payload.rs b/realtime/src/node/proposal_manager/l2_block_payload.rs new file mode 100644 index 00000000..6cb8e07a --- /dev/null +++ b/realtime/src/node/proposal_manager/l2_block_payload.rs @@ -0,0 +1,12 @@ +use alloy::primitives::B256; +use alloy::rpc::types::Transaction; + +pub struct L2BlockV2Payload { + pub coinbase: alloy::primitives::Address, + pub tx_list: Vec, + pub timestamp_sec: u64, + pub gas_limit_without_anchor: u64, + pub anchor_block_id: u64, + pub anchor_block_hash: B256, + pub anchor_state_root: B256, +} diff --git a/realtime/src/node/proposal_manager/mod.rs b/realtime/src/node/proposal_manager/mod.rs new file mode 100644 index 00000000..5a32445a --- /dev/null +++ b/realtime/src/node/proposal_manager/mod.rs @@ -0,0 +1,575 @@ +mod async_submitter; +mod batch_builder; +pub mod bridge_handler; +pub mod l2_block_payload; +pub mod proposal; + +use crate::l1::bindings::ICheckpointStore::Checkpoint; +use crate::l2::execution_layer::L2BridgeHandlerOps; +use crate::node::proposal_manager::bridge_handler::UserOp; +use crate::raiko::RaikoClient; +use crate::{l1::execution_layer::ExecutionLayer, l2::taiko::Taiko}; +use alloy::primitives::aliases::U48; +use alloy::primitives::{B256, FixedBytes}; +use anyhow::Error; +use async_submitter::AsyncSubmitter; +use batch_builder::BatchBuilder; +use bridge_handler::BridgeHandler; +use common::metrics::Metrics; +use common::{batch_builder::BatchBuilderConfig, shared::l2_slot_info_v2::L2SlotContext}; +use common::{ + l1::{ethereum_l1::EthereumL1, traits::ELTrait}, + l2::taiko_driver::{OperationType, models::BuildPreconfBlockResponse}, + shared::{ + anchor_block_info::AnchorBlockInfo, l2_block_v2::L2BlockV2Draft, + l2_tx_lists::PreBuiltTxList, + }, + utils::cancellation_token::CancellationToken, +}; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::{net::SocketAddr, sync::Arc}; +use tokio::sync::Mutex; +use tracing::{debug, error, info, warn}; + +use crate::node::L2SlotInfoV2; + +const MIN_ANCHOR_OFFSET: u64 = 2; + +pub struct BatchManager { + batch_builder: BatchBuilder, + async_submitter: AsyncSubmitter, + bridge_handler: Arc>, + ethereum_l1: Arc>, + pub taiko: Arc, + l1_height_lag: u64, + #[allow(dead_code)] + metrics: Arc, + #[allow(dead_code)] + cancel_token: CancellationToken, + last_finalized_block_hash: B256, + last_finalized_block_number: Arc, +} + +impl BatchManager { + #[allow(clippy::too_many_arguments)] + pub async fn new( + l1_height_lag: u64, + config: BatchBuilderConfig, + ethereum_l1: Arc>, + taiko: Arc, + metrics: Arc, + cancel_token: CancellationToken, + last_finalized_block_hash: B256, + raiko_client: RaikoClient, + basefee_sharing_pctg: u8, + proof_request_bypass: bool, + bridge_rpc_addr: String, + l1_chain_id: u64, + l2_chain_id: u64, + ) -> Result { + info!( + "Batch builder config:\n\ + max_bytes_size_of_batch: {}\n\ + max_blocks_per_batch: {}\n\ + l1_slot_duration_sec: {}\n\ + max_time_shift_between_blocks_sec: {}\n\ + max_anchor_height_offset: {}", + config.max_bytes_size_of_batch, + config.max_blocks_per_batch, + config.l1_slot_duration_sec, + config.max_time_shift_between_blocks_sec, + config.max_anchor_height_offset, + ); + + let bridge_addr: SocketAddr = bridge_rpc_addr.parse().map_err(|e| { + anyhow::anyhow!( + "Failed to parse BRIDGE_RPC_ADDR '{}': {}", + bridge_rpc_addr, + e + ) + })?; + + let last_finalized_block_number = Arc::new(AtomicU64::new(0)); + + let bridge_handler = Arc::new(Mutex::new( + BridgeHandler::new( + bridge_addr, + ethereum_l1.clone(), + taiko.clone(), + cancel_token.clone(), + l1_chain_id, + l2_chain_id, + last_finalized_block_number.clone(), + ) + .await?, + )); + + let async_submitter = AsyncSubmitter::new( + raiko_client, + basefee_sharing_pctg, + ethereum_l1.clone(), + proof_request_bypass, + ); + + Ok(Self { + batch_builder: BatchBuilder::new( + config, + ethereum_l1.slot_clock.clone(), + metrics.clone(), + ), + async_submitter, + bridge_handler, + ethereum_l1, + taiko, + l1_height_lag, + metrics, + cancel_token, + last_finalized_block_hash, + last_finalized_block_number, + }) + } + + /// Non-blocking poll: check if the in-flight submission has completed. + /// On success, updates `last_finalized_block_hash`. Returns None if idle or still in progress. + pub fn poll_submission_result(&mut self) -> Option> { + match self.async_submitter.try_recv_result() { + Some(Ok(result)) => { + info!( + "Submission completed. New last finalized block: number={}, hash={}", + result.new_last_finalized_block_number, result.new_last_finalized_block_hash, + ); + self.last_finalized_block_hash = result.new_last_finalized_block_hash; + self.last_finalized_block_number + .store(result.new_last_finalized_block_number, Ordering::Relaxed); + Some(Ok(())) + } + Some(Err(e)) => Some(Err(e)), + None => None, + } + } + + /// Kick off an async submission if there's a finalized batch ready and the submitter is idle. + pub async fn try_start_submission( + &mut self, + submit_only_full_batches: bool, + ) -> Result<(), Error> { + if self.async_submitter.is_busy() { + return Ok(()); + } + + self.batch_builder + .finalize_if_needed(submit_only_full_batches); + + let Some(batch) = self + .batch_builder + .pop_oldest_batch(self.last_finalized_block_hash) + else { + return Ok(()); + }; + + // Check no L1 tx already in progress + if self + .ethereum_l1 + .execution_layer + .is_transaction_in_progress() + .await? + { + debug!("Cannot submit batch, L1 transaction already in progress. Re-queuing."); + self.batch_builder.push_front_batch(batch); + return Ok(()); + } + + let status_store = self.bridge_handler.lock().await.status_store(); + + info!( + "Starting async submission: {} blocks, last_finalized_block_hash: {}", + batch.l2_blocks.len(), + batch.last_finalized_block_hash, + ); + + self.async_submitter.submit(batch, Some(status_store)); + Ok(()) + } + + pub fn is_submission_in_progress(&self) -> bool { + self.async_submitter.is_busy() + } + + /// Drop all finalized batches without submitting. Used in PRECONF_ONLY mode. + pub fn drain_finalized_batches(&mut self) { + self.batch_builder.finalize_if_needed(false); + while let Some(batch) = self + .batch_builder + .pop_oldest_batch(self.last_finalized_block_hash) + { + info!( + "PRECONF_ONLY: dropping batch with {} blocks", + batch.l2_blocks.len(), + ); + } + } + + pub fn should_new_block_be_created( + &self, + pending_tx_list: &Option, + l2_slot_context: &L2SlotContext, + ) -> bool { + self.batch_builder.should_new_block_be_created( + pending_tx_list, + l2_slot_context.info.slot_timestamp(), + l2_slot_context.end_of_sequencing, + ) + } + + pub async fn preconfirm_block( + &mut self, + pending_tx_list: Option, + l2_slot_context: &L2SlotContext, + ) -> Result { + let result = self + .add_new_l2_block( + pending_tx_list.unwrap_or_else(PreBuiltTxList::empty), + l2_slot_context, + OperationType::Preconfirm, + ) + .await?; + if self + .batch_builder + .is_greater_than_max_anchor_height_offset()? + { + info!("Maximum allowed anchor height offset exceeded, finalizing current batch."); + self.batch_builder.finalize_current_batch(); + } + + Ok(result) + } + + async fn add_new_l2_block( + &mut self, + prebuilt_tx_list: PreBuiltTxList, + l2_slot_context: &L2SlotContext, + operation_type: OperationType, + ) -> Result { + let timestamp = l2_slot_context.info.slot_timestamp(); + if let Some(last_block_timestamp) = self + .batch_builder + .get_current_proposal_last_block_timestamp() + && timestamp == last_block_timestamp + { + return Err(anyhow::anyhow!( + "Cannot add another block with the same timestamp as the last block, timestamp: {timestamp}, last block timestamp: {last_block_timestamp}" + )); + } + + info!( + "Adding new L2 block id: {}, timestamp: {}", + l2_slot_context.info.parent_id() + 1, + timestamp, + ); + + let l2_draft_block = L2BlockV2Draft { + prebuilt_tx_list: prebuilt_tx_list.clone(), + timestamp_sec: timestamp, + gas_limit_without_anchor: l2_slot_context.info.parent_gas_limit_without_anchor(), + }; + + if !self.batch_builder.can_consume_l2_block(&l2_draft_block) { + let _ = self.create_new_batch().await?; + } + + let preconfed_block = self + .add_draft_block_to_proposal(l2_draft_block, l2_slot_context, operation_type) + .await?; + + Ok(preconfed_block) + } + + pub async fn has_pending_user_ops(&self) -> bool { + self.bridge_handler.lock().await.has_pending_user_ops() + } + + /// Process all pending UserOps: route each to L1 or L2 based on its chainId field. + /// + /// - L1→L2 deposits: UserOp added to proposal (for L1 multicall), processMessage tx added to L2 block + /// - L2 direct (bridge-out): UserOp execution tx added to L2 block, L2→L1 relay handled post-execution + async fn add_pending_user_ops_to_draft_block( + &mut self, + l2_draft_block: &mut L2BlockV2Draft, + ) -> Result)>, anyhow::Error> { + use bridge_handler::UserOpRouting; + + let (routing, status_store) = { + let mut handler = self.bridge_handler.lock().await; + let routing = handler.next_user_op_routed().await?; + (routing, handler.status_store()) + }; + + let Some(routing) = routing else { + return Ok(None); + }; + + match routing { + UserOpRouting::L1ToL2 { user_op, l2_call } => { + info!("Processing L1→L2 deposit: UserOp id={}", user_op.id); + + let l2_call_bridge_tx = self + .taiko + .l2_execution_layer() + .construct_l2_call_tx(l2_call.message_from_l1) + .await?; + + info!("Inserting processMessage tx into L2 block"); + l2_draft_block + .prebuilt_tx_list + .tx_list + .push(l2_call_bridge_tx); + + Ok(Some((user_op, l2_call.signal_slot_on_l2))) + } + UserOpRouting::L2Direct { user_op } => { + info!( + "Processing L2 UserOp (bridge-out): id={} submitter={}", + user_op.id, user_op.submitter + ); + + match self + .taiko + .l2_execution_layer() + .construct_l2_user_op_tx(&user_op) + .await + { + Ok(tx) => { + // Track L2 UserOp ID first — only insert tx if tracking succeeds, + // otherwise we'd execute on L2 but show Rejected in the status store. + if let Err(e) = self.batch_builder.add_l2_user_op_id(user_op.id) { + error!( + "Failed to track L2 UserOp id={}: {}. Dropping tx.", + user_op.id, e + ); + status_store.set( + user_op.id, + &bridge_handler::UserOpStatus::Rejected { + reason: format!("Failed to track UserOp: {}", e), + }, + ); + } else { + info!("Inserting L2 UserOp execution tx into block"); + l2_draft_block.prebuilt_tx_list.tx_list.push(tx); + } + } + Err(e) => { + error!("Failed to construct L2 UserOp tx: {}", e); + status_store.set( + user_op.id, + &bridge_handler::UserOpStatus::Rejected { + reason: format!("Failed to construct L2 tx: {}", e), + }, + ); + } + } + // No L1 UserOp or signal slot for L2-direct ops + Ok(None) + } + } + } + + async fn add_draft_block_to_proposal( + &mut self, + mut l2_draft_block: L2BlockV2Draft, + l2_slot_context: &L2SlotContext, + operation_type: OperationType, + ) -> Result { + let mut anchor_signal_slots: Vec> = vec![]; + + debug!("Checking for pending UserOps (L1→L2 deposits and L2 direct)"); + if let Some((user_op_data, signal_slot)) = self + .add_pending_user_ops_to_draft_block(&mut l2_draft_block) + .await? + { + self.batch_builder.add_user_op(user_op_data)?; + self.batch_builder.add_signal_slot(signal_slot)?; + anchor_signal_slots.push(signal_slot); + } else { + debug!("No L1→L2 UserOps (L2 direct ops, if any, were handled inline)"); + } + + let payload = self.batch_builder.add_l2_draft_block(l2_draft_block)?; + + match self + .taiko + .advance_head_to_new_l2_block( + payload, + l2_slot_context, + anchor_signal_slots, + operation_type, + ) + .await + { + Ok(preconfed_block) => { + self.batch_builder.set_proposal_checkpoint(Checkpoint { + blockNumber: U48::from(preconfed_block.number), + stateRoot: preconfed_block.state_root, + blockHash: preconfed_block.hash, + })?; + + debug!("Checking for initiated L1 calls"); + if let Some(l1_call) = self + .bridge_handler + .lock() + .await + .find_l1_call(preconfed_block.number, preconfed_block.state_root) + .await? + { + self.batch_builder.add_l1_call(l1_call)?; + } else { + debug!("No L1 calls initiated"); + } + + Ok(preconfed_block) + } + Err(err) => { + error!("Failed to advance head to new L2 block: {}", err); + self.remove_last_l2_block(); + Err(anyhow::anyhow!( + "Failed to advance head to new L2 block: {}", + err + )) + } + } + } + + async fn create_new_batch(&mut self) -> Result { + let last_anchor_id = self + .taiko + .l2_execution_layer() + .get_last_synced_anchor_block_id_from_geth() + .await + .unwrap_or_else(|e| { + warn!("Failed to get last synced anchor block ID from Taiko Geth: {e}"); + 0 + }); + let anchor_block_info = AnchorBlockInfo::from_chain_state( + self.ethereum_l1.execution_layer.common(), + self.l1_height_lag, + last_anchor_id, + MIN_ANCHOR_OFFSET, + ) + .await?; + + let anchor_block_id = anchor_block_info.id(); + // Use B256::ZERO as placeholder -- real last_finalized_block_hash is stamped at submission time + self.batch_builder + .create_new_batch(anchor_block_info, B256::ZERO); + + Ok(anchor_block_id) + } + + fn remove_last_l2_block(&mut self) { + self.batch_builder.remove_last_l2_block(); + } + + pub async fn reset_builder(&mut self) -> Result<(), Error> { + warn!("Resetting batch builder"); + + self.async_submitter.abort(); + + self.batch_builder = batch_builder::BatchBuilder::new( + self.batch_builder.get_config().clone(), + self.ethereum_l1.slot_clock.clone(), + self.metrics.clone(), + ); + + Ok(()) + } + + pub fn has_batches(&self) -> bool { + !self.batch_builder.is_empty() + } + + pub fn get_number_of_batches(&self) -> u64 { + self.batch_builder.get_number_of_batches() + } + + /// Reorg all unproposed L2 blocks back to the last proposed block. + /// Called on startup to clean up any preconfirmed-but-unproposed blocks. + pub async fn reorg_unproposed_blocks(&mut self) -> Result<(), Error> { + let last_finalized_hash = self + .ethereum_l1 + .execution_layer + .get_last_finalized_block_hash() + .await?; + + if last_finalized_hash == B256::ZERO { + info!("No finalized block hash on L1 (genesis). Nothing to reorg."); + return Ok(()); + } + + let last_proposed_block_number = match self + .taiko + .find_l2_block_number_by_hash(last_finalized_hash) + .await + { + Ok(n) => n, + Err(_) => { + info!( + "lastFinalizedBlockHash {} not found on L2 — treating as no blocks proposed yet", + last_finalized_hash + ); + 0 + } + }; + + let l2_head = self.taiko.get_latest_l2_block_id().await?; + + // Always update the shared finalized block number for RPC status queries + self.last_finalized_block_number + .store(last_proposed_block_number, Ordering::Relaxed); + + if l2_head <= last_proposed_block_number { + info!( + "No unproposed blocks: L2 head {} <= last proposed {}", + l2_head, last_proposed_block_number + ); + return Ok(()); + } + + let gap = l2_head - last_proposed_block_number; + warn!( + "Detected {} unproposed L2 blocks ({} to {}). Reorging to last proposed block {}.", + gap, + last_proposed_block_number + 1, + l2_head, + last_proposed_block_number + ); + + let reorg_result = self + .taiko + .reorg_stale_block(last_proposed_block_number) + .await?; + info!( + "Reorg complete: new head hash={}, blocks removed={}", + reorg_result.new_head_block_hash, reorg_result.blocks_removed + ); + + self.last_finalized_block_hash = last_finalized_hash; + Ok(()) + } + + #[allow(dead_code)] + pub async fn reanchor_block( + &mut self, + pending_tx_list: PreBuiltTxList, + l2_slot_info: L2SlotInfoV2, + ) -> Result { + let l2_slot_context = L2SlotContext { + info: l2_slot_info, + end_of_sequencing: false, + }; + + let block = self + .add_new_l2_block(pending_tx_list, &l2_slot_context, OperationType::Reanchor) + .await?; + + Ok(block) + } +} diff --git a/realtime/src/node/proposal_manager/proposal.rs b/realtime/src/node/proposal_manager/proposal.rs new file mode 100644 index 00000000..39e5b059 --- /dev/null +++ b/realtime/src/node/proposal_manager/proposal.rs @@ -0,0 +1,112 @@ +use crate::l1::bindings::ICheckpointStore::Checkpoint; +use crate::node::proposal_manager::{ + bridge_handler::{L1Call, UserOp}, + l2_block_payload::L2BlockV2Payload, +}; +use alloy::primitives::{Address, B256, FixedBytes}; +use common::shared::l2_block_v2::{L2BlockV2, L2BlockV2Draft}; +use std::collections::VecDeque; +use std::time::Instant; +use taiko_protocol::shasta::manifest::{BlockManifest, DerivationSourceManifest}; +use tracing::{debug, warn}; + +#[allow(dead_code)] +pub type Proposals = VecDeque; + +#[derive(Default, Clone)] +pub struct Proposal { + pub l2_blocks: Vec, + pub total_bytes: u64, + pub coinbase: Address, + + // RealTime: maxAnchor instead of anchor + pub max_anchor_block_number: u64, + pub max_anchor_block_hash: B256, + pub max_anchor_state_root: B256, + + // Proof fields + pub checkpoint: Checkpoint, + pub last_finalized_block_hash: B256, + + // Surge POC fields (carried over) + pub user_ops: Vec, + pub l2_user_op_ids: Vec, + pub signal_slots: Vec>, + pub l1_calls: Vec, + + // ZK proof (populated after Raiko call) + pub zk_proof: Option>, +} + +impl Proposal { + pub fn compress(&mut self) { + let start = Instant::now(); + + let mut block_manifests = >::with_capacity(self.l2_blocks.len()); + for l2_block in &self.l2_blocks { + block_manifests.push(BlockManifest { + timestamp: l2_block.timestamp_sec, + coinbase: l2_block.coinbase, + anchor_block_number: l2_block.anchor_block_number, + gas_limit: l2_block.gas_limit_without_anchor, + transactions: l2_block + .prebuilt_tx_list + .tx_list + .iter() + .map(|tx| tx.clone().into()) + .collect(), + }); + } + + let manifest = DerivationSourceManifest { + blocks: block_manifests, + }; + + let manifest_data = match manifest.encode_and_compress() { + Ok(data) => data, + Err(err) => { + warn!("Failed to compress proposal manifest: {err}"); + return; + } + }; + + debug!( + "Proposal compression completed in {} ms. Total bytes before: {}. Total bytes after: {}.", + start.elapsed().as_millis(), + self.total_bytes, + manifest_data.len() + ); + + self.total_bytes = manifest_data.len() as u64; + } + + fn create_block_from_draft(&mut self, l2_draft_block: L2BlockV2Draft) -> L2BlockV2 { + L2BlockV2::new_from( + l2_draft_block.prebuilt_tx_list, + l2_draft_block.timestamp_sec, + self.coinbase, + self.max_anchor_block_number, + l2_draft_block.gas_limit_without_anchor, + ) + } + + pub fn add_l2_block(&mut self, l2_block: L2BlockV2) -> L2BlockV2Payload { + let l2_payload = L2BlockV2Payload { + coinbase: self.coinbase, + tx_list: l2_block.prebuilt_tx_list.tx_list.clone(), + timestamp_sec: l2_block.timestamp_sec, + gas_limit_without_anchor: l2_block.gas_limit_without_anchor, + anchor_block_id: self.max_anchor_block_number, + anchor_block_hash: self.max_anchor_block_hash, + anchor_state_root: self.max_anchor_state_root, + }; + self.total_bytes += l2_block.prebuilt_tx_list.bytes_length; + self.l2_blocks.push(l2_block); + l2_payload + } + + pub fn add_l2_draft_block(&mut self, l2_draft_block: L2BlockV2Draft) -> L2BlockV2Payload { + let l2_block = self.create_block_from_draft(l2_draft_block); + self.add_l2_block(l2_block) + } +} diff --git a/realtime/src/raiko/mod.rs b/realtime/src/raiko/mod.rs new file mode 100644 index 00000000..1b4f7ede --- /dev/null +++ b/realtime/src/raiko/mod.rs @@ -0,0 +1,184 @@ +use crate::l1::bindings::ProofType; +use crate::utils::config::RealtimeConfig; +use anyhow::Error; +use reqwest::Client; +use serde::{Deserialize, Serialize}; +use std::time::Duration; +use tracing::{debug, info, warn}; + +#[derive(Clone)] +pub struct RaikoClient { + client: Client, + pub base_url: String, + pub api_key: Option, + pub proof_type: ProofType, + #[allow(dead_code)] + l2_network: String, + #[allow(dead_code)] + l1_network: String, + poll_interval: Duration, + max_retries: u32, +} + +#[derive(Serialize)] +pub struct RaikoProofRequest { + pub l2_block_numbers: Vec, + pub proof_type: String, + pub max_anchor_block_number: u64, + pub last_finalized_block_hash: String, + pub basefee_sharing_pctg: u8, + #[serde(skip_serializing_if = "Option::is_none")] + pub network: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub l1_network: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub prover: Option, + pub signal_slots: Vec, + pub sources: Vec, + pub blobs: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub checkpoint: Option, + pub blob_proof_type: String, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RaikoDerivationSource { + pub is_forced_inclusion: bool, + pub blob_slice: RaikoBlobSlice, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RaikoBlobSlice { + pub blob_hashes: Vec, + pub offset: u32, + pub timestamp: u64, +} + +#[derive(Serialize, Deserialize)] +pub struct RaikoCheckpoint { + pub block_number: u64, + pub block_hash: String, + pub state_root: String, +} + +#[derive(Deserialize)] +pub struct RaikoResponse { + pub status: String, + #[serde(default)] + pub data: Option, + #[serde(default)] + pub proof_type: Option, + #[serde(default)] + pub batch_id: Option, + #[serde(default)] + pub error: Option, + #[serde(default)] + pub message: Option, +} + +#[derive(Deserialize)] +#[serde(untagged)] +pub enum RaikoData { + Proof { proof: RaikoProof }, + Status { status: String }, +} + +#[derive(Deserialize)] +pub struct RaikoProof { + pub proof: Option, + #[serde(default)] + pub input: Option, + #[serde(default)] + pub quote: Option, + #[serde(default)] + pub uuid: Option, + #[serde(default)] + pub kzg_proof: Option, +} + +impl RaikoClient { + pub fn new(config: &RealtimeConfig) -> Self { + Self { + client: Client::new(), + base_url: config.raiko_url.clone(), + api_key: config.raiko_api_key.clone(), + proof_type: config.proof_type, + l2_network: config.raiko_network.clone(), + l1_network: config.raiko_l1_network.clone(), + poll_interval: Duration::from_millis(config.raiko_poll_interval_ms), + max_retries: config.raiko_max_retries, + } + } + + /// Request a proof and poll until ready. + /// Returns the raw proof bytes. + pub async fn get_proof(&self, request: &RaikoProofRequest) -> Result, Error> { + let url = format!("{}/v3/proof/batch/realtime", self.base_url); + + for attempt in 0..self.max_retries { + let mut req = self.client.post(&url).json(request); + + if let Some(ref key) = self.api_key { + req = req.header("X-API-KEY", key); + } + + let resp = req.send().await?; + let http_status = resp.status(); + let raw_body = resp.text().await?; + debug!( + "Raiko response (attempt {}): HTTP {} | body: {}", + attempt + 1, + http_status, + raw_body + ); + let body: RaikoResponse = serde_json::from_str(&raw_body).map_err(|e| { + anyhow::anyhow!( + "Failed to parse Raiko response (HTTP {}): {} | body: {}", + http_status, + e, + raw_body + ) + })?; + + if body.status == "error" { + return Err(anyhow::anyhow!( + "Raiko proof failed: {}", + body.message.unwrap_or_default() + )); + } + + match body.data { + Some(RaikoData::Proof { proof: proof_obj }) => { + let proof_hex = proof_obj.proof.ok_or_else(|| { + anyhow::anyhow!("Raiko returned proof object with null proof field") + })?; + info!("ZK proof received (attempt {})", attempt + 1); + let proof_bytes = hex::decode(proof_hex.trim_start_matches("0x"))?; + return Ok(proof_bytes); + } + Some(RaikoData::Status { ref status }) if status == "ZKAnyNotDrawn" => { + warn!("Raiko: ZK prover not drawn for this request"); + return Err(anyhow::anyhow!("ZK prover not drawn")); + } + Some(RaikoData::Status { ref status }) => { + debug!( + "Raiko status: {}, polling... (attempt {})", + status, + attempt + 1 + ); + tokio::time::sleep(self.poll_interval).await; + } + None => { + return Err(anyhow::anyhow!("Raiko: unexpected empty response")); + } + } + } + + Err(anyhow::anyhow!( + "Raiko: proof not ready after {} attempts", + self.max_retries + )) + } +} diff --git a/realtime/src/shared_abi/Bridge.json b/realtime/src/shared_abi/Bridge.json new file mode 100644 index 00000000..8f768573 --- /dev/null +++ b/realtime/src/shared_abi/Bridge.json @@ -0,0 +1,738 @@ +{ + "abi": [ + { + "type": "function", + "name": "context", + "inputs": [], + "outputs": [ + { + "name": "ctx_", + "type": "tuple", + "internalType": "struct IBridge.Context", + "components": [ + { + "name": "msgHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "srcChainId", + "type": "uint64", + "internalType": "uint64" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "failMessage", + "inputs": [ + { + "name": "_message", + "type": "tuple", + "internalType": "struct IBridge.Message", + "components": [ + { + "name": "id", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "fee", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "gasLimit", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "srcChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "srcOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "destChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "destOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "hashMessage", + "inputs": [ + { + "name": "_message", + "type": "tuple", + "internalType": "struct IBridge.Message", + "components": [ + { + "name": "id", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "fee", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "gasLimit", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "srcChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "srcOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "destChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "destOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "isMessageSent", + "inputs": [ + { + "name": "_message", + "type": "tuple", + "internalType": "struct IBridge.Message", + "components": [ + { + "name": "id", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "fee", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "gasLimit", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "srcChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "srcOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "destChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "destOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "nextMessageId", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint64", + "internalType": "uint64" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "processMessage", + "inputs": [ + { + "name": "_message", + "type": "tuple", + "internalType": "struct IBridge.Message", + "components": [ + { + "name": "id", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "fee", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "gasLimit", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "srcChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "srcOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "destChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "destOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "_proof", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "uint8", + "internalType": "enum IBridge.Status" + }, + { + "name": "", + "type": "uint8", + "internalType": "enum IBridge.StatusReason" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "recallMessage", + "inputs": [ + { + "name": "_message", + "type": "tuple", + "internalType": "struct IBridge.Message", + "components": [ + { + "name": "id", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "fee", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "gasLimit", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "srcChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "srcOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "destChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "destOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "_proof", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "retryMessage", + "inputs": [ + { + "name": "_message", + "type": "tuple", + "internalType": "struct IBridge.Message", + "components": [ + { + "name": "id", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "fee", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "gasLimit", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "srcChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "srcOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "destChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "destOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "_isLastAttempt", + "type": "bool", + "internalType": "bool" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "sendMessage", + "inputs": [ + { + "name": "_message", + "type": "tuple", + "internalType": "struct IBridge.Message", + "components": [ + { + "name": "id", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "fee", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "gasLimit", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "srcChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "srcOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "destChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "destOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [ + { + "name": "msgHash_", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "message_", + "type": "tuple", + "internalType": "struct IBridge.Message", + "components": [ + { + "name": "id", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "fee", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "gasLimit", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "srcChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "srcOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "destChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "destOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "stateMutability": "payable" + }, + { + "type": "event", + "name": "MessageSent", + "inputs": [ + { + "name": "msgHash", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "message", + "type": "tuple", + "indexed": false, + "internalType": "struct IBridge.Message", + "components": [ + { + "name": "id", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "fee", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "gasLimit", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "srcChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "srcOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "destChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "destOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "MessageStatusChanged", + "inputs": [ + { + "name": "msgHash", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "status", + "type": "uint8", + "indexed": false, + "internalType": "enum IBridge.Status" + } + ], + "anonymous": false + } + ] +} \ No newline at end of file diff --git a/realtime/src/shared_abi/SignalService.json b/realtime/src/shared_abi/SignalService.json new file mode 100644 index 00000000..05a35bb2 --- /dev/null +++ b/realtime/src/shared_abi/SignalService.json @@ -0,0 +1 @@ +{"abi":[{"type":"function","name":"getCheckpoint","inputs":[{"name":"_blockNumber","type":"uint48","internalType":"uint48"}],"outputs":[{"name":"","type":"tuple","internalType":"struct ICheckpointStore.Checkpoint","components":[{"name":"blockNumber","type":"uint48","internalType":"uint48"},{"name":"blockHash","type":"bytes32","internalType":"bytes32"},{"name":"stateRoot","type":"bytes32","internalType":"bytes32"}]}],"stateMutability":"view"},{"type":"function","name":"isSignalSent","inputs":[{"name":"_app","type":"address","internalType":"address"},{"name":"_signal","type":"bytes32","internalType":"bytes32"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"isSignalSent","inputs":[{"name":"_signalSlot","type":"bytes32","internalType":"bytes32"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"proveSignalReceived","inputs":[{"name":"_chainId","type":"uint64","internalType":"uint64"},{"name":"_app","type":"address","internalType":"address"},{"name":"_signal","type":"bytes32","internalType":"bytes32"},{"name":"_proof","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"numCacheOps_","type":"uint256","internalType":"uint256"}],"stateMutability":"nonpayable"},{"type":"function","name":"saveCheckpoint","inputs":[{"name":"_checkpoint","type":"tuple","internalType":"struct ICheckpointStore.Checkpoint","components":[{"name":"blockNumber","type":"uint48","internalType":"uint48"},{"name":"blockHash","type":"bytes32","internalType":"bytes32"},{"name":"stateRoot","type":"bytes32","internalType":"bytes32"}]}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"sendSignal","inputs":[{"name":"_signal","type":"bytes32","internalType":"bytes32"}],"outputs":[{"name":"slot_","type":"bytes32","internalType":"bytes32"}],"stateMutability":"nonpayable"},{"type":"function","name":"setSignalsReceived","inputs":[{"name":"_signalSlots","type":"bytes32[]","internalType":"bytes32[]"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"verifySignalReceived","inputs":[{"name":"_chainId","type":"uint64","internalType":"uint64"},{"name":"_app","type":"address","internalType":"address"},{"name":"_signal","type":"bytes32","internalType":"bytes32"},{"name":"_proof","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"view"},{"type":"event","name":"CheckpointSaved","inputs":[{"name":"blockNumber","type":"uint48","indexed":true,"internalType":"uint48"},{"name":"blockHash","type":"bytes32","indexed":false,"internalType":"bytes32"},{"name":"stateRoot","type":"bytes32","indexed":false,"internalType":"bytes32"}],"anonymous":false},{"type":"event","name":"SignalSent","inputs":[{"name":"app","type":"address","indexed":false,"internalType":"address"},{"name":"signal","type":"bytes32","indexed":false,"internalType":"bytes32"},{"name":"slot","type":"bytes32","indexed":false,"internalType":"bytes32"},{"name":"value","type":"bytes32","indexed":false,"internalType":"bytes32"}],"anonymous":false}]} diff --git a/realtime/src/shared_abi/bindings.rs b/realtime/src/shared_abi/bindings.rs new file mode 100644 index 00000000..56bc1268 --- /dev/null +++ b/realtime/src/shared_abi/bindings.rs @@ -0,0 +1,33 @@ +#![allow(clippy::too_many_arguments)] + +use alloy::sol; + +sol!( + #[allow(missing_docs)] + #[sol(rpc)] + #[derive(Debug)] + Bridge, + "src/shared_abi/Bridge.json" +); + +sol!( + #[allow(missing_docs)] + #[sol(rpc)] + #[derive(Debug)] + SignalService, + "src/shared_abi/SignalService.json" +); + +// HopProof encoding struct for cross-chain signal verification via storage proofs. +// Not part of the SignalService ABI directly — it is the encoding format for the +// `_proof` bytes parameter in proveSignalReceived / verifySignalReceived. +sol! { + struct HopProof { + uint64 chainId; + uint64 blockId; + bytes32 rootHash; + uint8 cacheOption; + bytes[] accountProof; + bytes[] storageProof; + } +} diff --git a/realtime/src/shared_abi/mod.rs b/realtime/src/shared_abi/mod.rs new file mode 100644 index 00000000..90c70dcc --- /dev/null +++ b/realtime/src/shared_abi/mod.rs @@ -0,0 +1 @@ +pub mod bindings; diff --git a/realtime/src/utils/config.rs b/realtime/src/utils/config.rs new file mode 100644 index 00000000..01ed2b92 --- /dev/null +++ b/realtime/src/utils/config.rs @@ -0,0 +1,103 @@ +use crate::l1::bindings::ProofType; +use alloy::primitives::Address; +use anyhow::Error; +use common::config::{ConfigTrait, address_parse_error}; +use std::str::FromStr; + +#[derive(Debug, Clone)] +pub struct RealtimeConfig { + pub realtime_inbox: Address, + pub proposer_multicall: Address, + pub bridge: Address, + pub raiko_url: String, + pub raiko_api_key: Option, + pub proof_type: ProofType, + pub raiko_network: String, + pub raiko_l1_network: String, + pub raiko_poll_interval_ms: u64, + pub raiko_max_retries: u32, + pub bridge_rpc_addr: String, + pub preconf_only: bool, + pub proof_request_bypass: bool, +} + +impl ConfigTrait for RealtimeConfig { + fn read_env_variables() -> Result { + let read_contract_address = |env_var: &str| -> Result { + let address_str = std::env::var(env_var) + .map_err(|e| anyhow::anyhow!("Failed to read {}: {}", env_var, e))?; + Address::from_str(&address_str) + .map_err(|e| address_parse_error(env_var, e, &address_str)) + }; + + let realtime_inbox = read_contract_address("REALTIME_INBOX_ADDRESS")?; + let proposer_multicall = read_contract_address("PROPOSER_MULTICALL_ADDRESS")?; + let bridge = read_contract_address("L1_BRIDGE_ADDRESS")?; + + let raiko_url = + std::env::var("RAIKO_URL").unwrap_or_else(|_| "http://localhost:8080".to_string()); + let raiko_api_key = std::env::var("RAIKO_API_KEY").ok(); + let proof_type: ProofType = std::env::var("PROOF_TYPE") + .unwrap_or_else(|_| "sp1".to_string()) + .parse()?; + let raiko_network = + std::env::var("RAIKO_L2_NETWORK").unwrap_or_else(|_| "taiko_mainnet".to_string()); + let raiko_l1_network = + std::env::var("RAIKO_L1_NETWORK").unwrap_or_else(|_| "ethereum".to_string()); + + let raiko_poll_interval_ms: u64 = std::env::var("RAIKO_POLL_INTERVAL_MS") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(2000); + + let raiko_max_retries: u32 = std::env::var("RAIKO_MAX_RETRIES") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(60); + + let bridge_rpc_addr = + std::env::var("BRIDGE_RPC_ADDR").unwrap_or_else(|_| "0.0.0.0:4545".to_string()); + + let preconf_only = std::env::var("PRECONF_ONLY") + .map(|v| v.to_lowercase() != "false" && v != "0") + .unwrap_or(true); + + let proof_request_bypass = std::env::var("PROOF_REQUEST_BYPASS") + .map(|v| v.to_lowercase() != "false" && v != "0") + .unwrap_or(false); + + Ok(RealtimeConfig { + realtime_inbox, + proposer_multicall, + bridge, + raiko_url, + raiko_api_key, + proof_type, + raiko_network, + raiko_l1_network, + raiko_poll_interval_ms, + raiko_max_retries, + bridge_rpc_addr, + preconf_only, + proof_request_bypass, + }) + } +} + +use std::fmt; +impl fmt::Display for RealtimeConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "RealTime inbox: {:#?}", self.realtime_inbox)?; + writeln!(f, "Proposer multicall: {:#?}", self.proposer_multicall)?; + writeln!(f, "Raiko URL: {}", self.raiko_url)?; + writeln!( + f, + "Proof type: {} (bit flag: {})", + self.proof_type, + self.proof_type.proof_bit_flag() + )?; + writeln!(f, "Preconf only: {}", self.preconf_only)?; + writeln!(f, "Proof request bypass: {}", self.proof_request_bypass)?; + Ok(()) + } +} diff --git a/realtime/src/utils/mod.rs b/realtime/src/utils/mod.rs new file mode 100644 index 00000000..ef68c369 --- /dev/null +++ b/realtime/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod config; diff --git a/shasta/Cargo.toml b/shasta/Cargo.toml index 6874966d..ab42677e 100644 --- a/shasta/Cargo.toml +++ b/shasta/Cargo.toml @@ -12,10 +12,12 @@ alloy-json-rpc = { workspace = true } anyhow = { workspace = true } common = { workspace = true } hex = { workspace = true } +jsonrpsee = { workspace = true } pacaya = { workspace = true } reqwest = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } +sled = { workspace = true } taiko_alethia_reth = { workspace = true } taiko_bindings = { workspace = true } taiko_protocol = { workspace = true } diff --git a/shasta/src/config/mod.rs b/shasta/src/config/mod.rs index d9279b87..a87a00bf 100644 --- a/shasta/src/config/mod.rs +++ b/shasta/src/config/mod.rs @@ -6,6 +6,8 @@ use std::str::FromStr; #[derive(Debug, Clone)] pub struct ShastaConfig { pub shasta_inbox: Address, + pub proposer_multicall: Address, + pub bridge: Address, pub handover_window_slots: u64, pub handover_start_buffer_ms: u64, pub l1_height_lag: u64, @@ -24,6 +26,8 @@ impl ConfigTrait for ShastaConfig { }; let shasta_inbox = read_contract_address("SHASTA_INBOX_ADDRESS")?; + let proposer_multicall = read_contract_address("PROPOSER_MULTICALL_ADDRESS")?; + let bridge = read_contract_address("L1_BRIDGE_ADDRESS")?; let handover_window_slots = std::env::var("HANDOVER_WINDOW_SLOTS") .unwrap_or("8".to_string()) @@ -63,6 +67,8 @@ impl ConfigTrait for ShastaConfig { Ok(ShastaConfig { shasta_inbox, + proposer_multicall, + bridge, handover_window_slots, handover_start_buffer_ms, l1_height_lag, diff --git a/shasta/src/l1/abi/Multicall.json b/shasta/src/l1/abi/Multicall.json new file mode 100644 index 00000000..53b423ce --- /dev/null +++ b/shasta/src/l1/abi/Multicall.json @@ -0,0 +1,44 @@ +{ + "abi": [ + { + "type": "receive", + "stateMutability": "payable" + }, + { + "type": "function", + "name": "multicall", + "inputs": [ + { + "name": "calls", + "type": "tuple[]", + "internalType": "struct Multicall.Call[]", + "components": [ + { + "name": "target", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [ + { + "name": "results", + "type": "bytes[]", + "internalType": "bytes[]" + } + ], + "stateMutability": "payable" + } + ] +} \ No newline at end of file diff --git a/shasta/src/l1/abi/SurgeInbox.json b/shasta/src/l1/abi/SurgeInbox.json new file mode 100644 index 00000000..f588cb21 --- /dev/null +++ b/shasta/src/l1/abi/SurgeInbox.json @@ -0,0 +1,2123 @@ +{ + "abi": [ + { + "type": "constructor", + "inputs": [ + { + "name": "_config", + "type": "tuple", + "internalType": "struct IInbox.Config", + "components": [ + { + "name": "proofVerifier", + "type": "address", + "internalType": "address" + }, + { + "name": "proposerChecker", + "type": "address", + "internalType": "address" + }, + { + "name": "proverWhitelist", + "type": "address", + "internalType": "address" + }, + { + "name": "signalService", + "type": "address", + "internalType": "address" + }, + { + "name": "bondToken", + "type": "address", + "internalType": "address" + }, + { + "name": "minBond", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "livenessBond", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "withdrawalDelay", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "provingWindow", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "permissionlessProvingDelay", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "maxProofSubmissionDelay", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "ringBufferSize", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "basefeeSharingPctg", + "type": "uint8", + "internalType": "uint8" + }, + { + "name": "forcedInclusionDelay", + "type": "uint16", + "internalType": "uint16" + }, + { + "name": "forcedInclusionFeeInGwei", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "forcedInclusionFeeDoubleThreshold", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "permissionlessInclusionMultiplier", + "type": "uint8", + "internalType": "uint8" + } + ] + }, + { + "name": "_maxFinalizationDelayBeforeStreakReset", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "_maxFinalizationDelayBeforeRollback", + "type": "uint48", + "internalType": "uint48" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "acceptOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "activate", + "inputs": [ + { + "name": "_lastPacayaBlockHash", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "activationTimestamp", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint48", + "internalType": "uint48" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "cancelWithdrawal", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "decodeCommitments", + "inputs": [ + { + "name": "_data", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "tuple[]", + "internalType": "struct IInbox.Commitment[]", + "components": [ + { + "name": "firstProposalId", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "firstProposalParentBlockHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "lastProposalHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "actualProver", + "type": "address", + "internalType": "address" + }, + { + "name": "endBlockNumber", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "endStateRoot", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "transitions", + "type": "tuple[]", + "internalType": "struct IInbox.Transition[]", + "components": [ + { + "name": "proposer", + "type": "address", + "internalType": "address" + }, + { + "name": "timestamp", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "blockHash", + "type": "bytes32", + "internalType": "bytes32" + } + ] + } + ] + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "decodeProposeInput", + "inputs": [ + { + "name": "_data", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "input_", + "type": "tuple", + "internalType": "struct IInbox.ProposeInput", + "components": [ + { + "name": "deadline", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "blobReference", + "type": "tuple", + "internalType": "struct LibBlobs.BlobReference", + "components": [ + { + "name": "blobStartIndex", + "type": "uint16", + "internalType": "uint16" + }, + { + "name": "numBlobs", + "type": "uint16", + "internalType": "uint16" + }, + { + "name": "offset", + "type": "uint24", + "internalType": "uint24" + } + ] + }, + { + "name": "numForcedInclusions", + "type": "uint16", + "internalType": "uint16" + } + ] + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "decodeProveInput", + "inputs": [ + { + "name": "_data", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "input_", + "type": "tuple", + "internalType": "struct IInbox.ProveInput", + "components": [ + { + "name": "commitment", + "type": "tuple", + "internalType": "struct IInbox.Commitment", + "components": [ + { + "name": "firstProposalId", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "firstProposalParentBlockHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "lastProposalHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "actualProver", + "type": "address", + "internalType": "address" + }, + { + "name": "endBlockNumber", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "endStateRoot", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "transitions", + "type": "tuple[]", + "internalType": "struct IInbox.Transition[]", + "components": [ + { + "name": "proposer", + "type": "address", + "internalType": "address" + }, + { + "name": "timestamp", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "blockHash", + "type": "bytes32", + "internalType": "bytes32" + } + ] + } + ] + } + ] + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "deposit", + "inputs": [ + { + "name": "_amount", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "depositTo", + "inputs": [ + { + "name": "_recipient", + "type": "address", + "internalType": "address" + }, + { + "name": "_amount", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "encodeCommitments", + "inputs": [ + { + "name": "_commitments", + "type": "tuple[]", + "internalType": "struct IInbox.Commitment[]", + "components": [ + { + "name": "firstProposalId", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "firstProposalParentBlockHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "lastProposalHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "actualProver", + "type": "address", + "internalType": "address" + }, + { + "name": "endBlockNumber", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "endStateRoot", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "transitions", + "type": "tuple[]", + "internalType": "struct IInbox.Transition[]", + "components": [ + { + "name": "proposer", + "type": "address", + "internalType": "address" + }, + { + "name": "timestamp", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "blockHash", + "type": "bytes32", + "internalType": "bytes32" + } + ] + } + ] + } + ], + "outputs": [ + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "encodeProposeInput", + "inputs": [ + { + "name": "_input", + "type": "tuple", + "internalType": "struct IInbox.ProposeInput", + "components": [ + { + "name": "deadline", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "blobReference", + "type": "tuple", + "internalType": "struct LibBlobs.BlobReference", + "components": [ + { + "name": "blobStartIndex", + "type": "uint16", + "internalType": "uint16" + }, + { + "name": "numBlobs", + "type": "uint16", + "internalType": "uint16" + }, + { + "name": "offset", + "type": "uint24", + "internalType": "uint24" + } + ] + }, + { + "name": "numForcedInclusions", + "type": "uint16", + "internalType": "uint16" + } + ] + } + ], + "outputs": [ + { + "name": "encoded_", + "type": "bytes", + "internalType": "bytes" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "encodeProveInput", + "inputs": [ + { + "name": "_input", + "type": "tuple", + "internalType": "struct IInbox.ProveInput", + "components": [ + { + "name": "commitment", + "type": "tuple", + "internalType": "struct IInbox.Commitment", + "components": [ + { + "name": "firstProposalId", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "firstProposalParentBlockHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "lastProposalHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "actualProver", + "type": "address", + "internalType": "address" + }, + { + "name": "endBlockNumber", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "endStateRoot", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "transitions", + "type": "tuple[]", + "internalType": "struct IInbox.Transition[]", + "components": [ + { + "name": "proposer", + "type": "address", + "internalType": "address" + }, + { + "name": "timestamp", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "blockHash", + "type": "bytes32", + "internalType": "bytes32" + } + ] + } + ] + } + ] + } + ], + "outputs": [ + { + "name": "encoded_", + "type": "bytes", + "internalType": "bytes" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "getBond", + "inputs": [ + { + "name": "_address", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "bond_", + "type": "tuple", + "internalType": "struct IBondManager.Bond", + "components": [ + { + "name": "balance", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "withdrawalRequestedAt", + "type": "uint48", + "internalType": "uint48" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getConfig", + "inputs": [], + "outputs": [ + { + "name": "config_", + "type": "tuple", + "internalType": "struct IInbox.Config", + "components": [ + { + "name": "proofVerifier", + "type": "address", + "internalType": "address" + }, + { + "name": "proposerChecker", + "type": "address", + "internalType": "address" + }, + { + "name": "proverWhitelist", + "type": "address", + "internalType": "address" + }, + { + "name": "signalService", + "type": "address", + "internalType": "address" + }, + { + "name": "bondToken", + "type": "address", + "internalType": "address" + }, + { + "name": "minBond", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "livenessBond", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "withdrawalDelay", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "provingWindow", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "permissionlessProvingDelay", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "maxProofSubmissionDelay", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "ringBufferSize", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "basefeeSharingPctg", + "type": "uint8", + "internalType": "uint8" + }, + { + "name": "forcedInclusionDelay", + "type": "uint16", + "internalType": "uint16" + }, + { + "name": "forcedInclusionFeeInGwei", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "forcedInclusionFeeDoubleThreshold", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "permissionlessInclusionMultiplier", + "type": "uint8", + "internalType": "uint8" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getCoreState", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct IInbox.CoreState", + "components": [ + { + "name": "nextProposalId", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "lastProposalBlockId", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "lastFinalizedProposalId", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "lastFinalizedTimestamp", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "lastCheckpointTimestamp", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "lastFinalizedBlockHash", + "type": "bytes32", + "internalType": "bytes32" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getCurrentForcedInclusionFee", + "inputs": [], + "outputs": [ + { + "name": "feeInGwei_", + "type": "uint64", + "internalType": "uint64" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getFinalizationStreak", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint48", + "internalType": "uint48" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getForcedInclusionState", + "inputs": [], + "outputs": [ + { + "name": "head_", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "tail_", + "type": "uint48", + "internalType": "uint48" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getForcedInclusions", + "inputs": [ + { + "name": "_start", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "_maxCount", + "type": "uint48", + "internalType": "uint48" + } + ], + "outputs": [ + { + "name": "inclusions_", + "type": "tuple[]", + "internalType": "struct IForcedInclusionStore.ForcedInclusion[]", + "components": [ + { + "name": "feeInGwei", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "blobSlice", + "type": "tuple", + "internalType": "struct LibBlobs.BlobSlice", + "components": [ + { + "name": "blobHashes", + "type": "bytes32[]", + "internalType": "bytes32[]" + }, + { + "name": "offset", + "type": "uint24", + "internalType": "uint24" + }, + { + "name": "timestamp", + "type": "uint48", + "internalType": "uint48" + } + ] + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getProposalHash", + "inputs": [ + { + "name": "_proposalId", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "hashCommitment", + "inputs": [ + { + "name": "_commitment", + "type": "tuple", + "internalType": "struct IInbox.Commitment", + "components": [ + { + "name": "firstProposalId", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "firstProposalParentBlockHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "lastProposalHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "actualProver", + "type": "address", + "internalType": "address" + }, + { + "name": "endBlockNumber", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "endStateRoot", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "transitions", + "type": "tuple[]", + "internalType": "struct IInbox.Transition[]", + "components": [ + { + "name": "proposer", + "type": "address", + "internalType": "address" + }, + { + "name": "timestamp", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "blockHash", + "type": "bytes32", + "internalType": "bytes32" + } + ] + } + ] + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "hashProposal", + "inputs": [ + { + "name": "_proposal", + "type": "tuple", + "internalType": "struct IInbox.Proposal", + "components": [ + { + "name": "id", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "timestamp", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "endOfSubmissionWindowTimestamp", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "proposer", + "type": "address", + "internalType": "address" + }, + { + "name": "parentProposalHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "originBlockNumber", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "originBlockHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "basefeeSharingPctg", + "type": "uint8", + "internalType": "uint8" + }, + { + "name": "sources", + "type": "tuple[]", + "internalType": "struct IInbox.DerivationSource[]", + "components": [ + { + "name": "isForcedInclusion", + "type": "bool", + "internalType": "bool" + }, + { + "name": "blobSlice", + "type": "tuple", + "internalType": "struct LibBlobs.BlobSlice", + "components": [ + { + "name": "blobHashes", + "type": "bytes32[]", + "internalType": "bytes32[]" + }, + { + "name": "offset", + "type": "uint24", + "internalType": "uint24" + }, + { + "name": "timestamp", + "type": "uint48", + "internalType": "uint48" + } + ] + } + ] + }, + { + "name": "signalSlotsHash", + "type": "bytes32", + "internalType": "bytes32" + } + ] + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "impl", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "inLimpMode", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "inNonReentrant", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "init", + "inputs": [ + { + "name": "_owner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "maxFinalizationDelayBeforeRollback", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint48", + "internalType": "uint48" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "maxFinalizationDelayBeforeStreakReset", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint48", + "internalType": "uint48" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "pause", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "paused", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "pendingOwner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "propose", + "inputs": [ + { + "name": "_lookahead", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "_data", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "proposeAndProve", + "inputs": [ + { + "name": "_lookahead", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "_proposeData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "_proveData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "_proof", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "proposeWithProof", + "inputs": [ + { + "name": "_lookahead", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "_data", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "_proof", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "_signalSlots", + "type": "bytes32[]", + "internalType": "bytes32[]" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "prove", + "inputs": [ + { + "name": "_data", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "_proof", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "proveConflicts", + "inputs": [ + { + "name": "_commitments", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "_proofs", + "type": "bytes[]", + "internalType": "bytes[]" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "proxiableUUID", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "requestWithdrawal", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "resolver", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "rollback", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "saveForcedInclusion", + "inputs": [ + { + "name": "_blobReference", + "type": "tuple", + "internalType": "struct LibBlobs.BlobReference", + "components": [ + { + "name": "blobStartIndex", + "type": "uint16", + "internalType": "uint16" + }, + { + "name": "numBlobs", + "type": "uint16", + "internalType": "uint16" + }, + { + "name": "offset", + "type": "uint24", + "internalType": "uint24" + } + ] + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "setLimpMode", + "inputs": [ + { + "name": "_val", + "type": "bool", + "internalType": "bool" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "unpause", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "upgradeTo", + "inputs": [ + { + "name": "newImplementation", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "upgradeToAndCall", + "inputs": [ + { + "name": "newImplementation", + "type": "address", + "internalType": "address" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "withdraw", + "inputs": [ + { + "name": "_to", + "type": "address", + "internalType": "address" + }, + { + "name": "_amount", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "AdminChanged", + "inputs": [ + { + "name": "previousAdmin", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "newAdmin", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "BeaconUpgraded", + "inputs": [ + { + "name": "beacon", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "BondDeposited", + "inputs": [ + { + "name": "depositor", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "recipient", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint64", + "indexed": false, + "internalType": "uint64" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "BondWithdrawn", + "inputs": [ + { + "name": "account", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint64", + "indexed": false, + "internalType": "uint64" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ConflictingProofsDetected", + "inputs": [ + { + "name": "firstProposalId", + "type": "uint48", + "indexed": true, + "internalType": "uint48" + }, + { + "name": "conflictingProofBitmap", + "type": "uint8", + "indexed": false, + "internalType": "LibProofBitmap.ProofBitmap" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ForcedInclusionSaved", + "inputs": [ + { + "name": "forcedInclusion", + "type": "tuple", + "indexed": false, + "internalType": "struct IForcedInclusionStore.ForcedInclusion", + "components": [ + { + "name": "feeInGwei", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "blobSlice", + "type": "tuple", + "internalType": "struct LibBlobs.BlobSlice", + "components": [ + { + "name": "blobHashes", + "type": "bytes32[]", + "internalType": "bytes32[]" + }, + { + "name": "offset", + "type": "uint24", + "internalType": "uint24" + }, + { + "name": "timestamp", + "type": "uint48", + "internalType": "uint48" + } + ] + } + ] + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "InboxActivated", + "inputs": [ + { + "name": "lastPacayaBlockHash", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Initialized", + "inputs": [ + { + "name": "version", + "type": "uint8", + "indexed": false, + "internalType": "uint8" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "LimpModeSet", + "inputs": [ + { + "name": "enabled", + "type": "bool", + "indexed": false, + "internalType": "bool" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "LivenessBondSettled", + "inputs": [ + { + "name": "payer", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "payee", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "livenessBond", + "type": "uint64", + "indexed": false, + "internalType": "uint64" + }, + { + "name": "credited", + "type": "uint64", + "indexed": false, + "internalType": "uint64" + }, + { + "name": "slashed", + "type": "uint64", + "indexed": false, + "internalType": "uint64" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferStarted", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Paused", + "inputs": [ + { + "name": "account", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Proposed", + "inputs": [ + { + "name": "id", + "type": "uint48", + "indexed": true, + "internalType": "uint48" + }, + { + "name": "proposer", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "parentProposalHash", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + }, + { + "name": "endOfSubmissionWindowTimestamp", + "type": "uint48", + "indexed": false, + "internalType": "uint48" + }, + { + "name": "basefeeSharingPctg", + "type": "uint8", + "indexed": false, + "internalType": "uint8" + }, + { + "name": "sources", + "type": "tuple[]", + "indexed": false, + "internalType": "struct IInbox.DerivationSource[]", + "components": [ + { + "name": "isForcedInclusion", + "type": "bool", + "internalType": "bool" + }, + { + "name": "blobSlice", + "type": "tuple", + "internalType": "struct LibBlobs.BlobSlice", + "components": [ + { + "name": "blobHashes", + "type": "bytes32[]", + "internalType": "bytes32[]" + }, + { + "name": "offset", + "type": "uint24", + "internalType": "uint24" + }, + { + "name": "timestamp", + "type": "uint48", + "internalType": "uint48" + } + ] + } + ] + }, + { + "name": "signalSlotsHash", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Proved", + "inputs": [ + { + "name": "firstProposalId", + "type": "uint48", + "indexed": false, + "internalType": "uint48" + }, + { + "name": "firstNewProposalId", + "type": "uint48", + "indexed": false, + "internalType": "uint48" + }, + { + "name": "lastProposalId", + "type": "uint48", + "indexed": false, + "internalType": "uint48" + }, + { + "name": "actualProver", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Rollbacked", + "inputs": [ + { + "name": "firstProposalId", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "lastProposalId", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Unpaused", + "inputs": [ + { + "name": "account", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Upgraded", + "inputs": [ + { + "name": "implementation", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "WithdrawalCancelled", + "inputs": [ + { + "name": "account", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "WithdrawalRequested", + "inputs": [ + { + "name": "account", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "withdrawableAt", + "type": "uint48", + "indexed": false, + "internalType": "uint48" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "ACCESS_DENIED", + "inputs": [] + }, + { + "type": "error", + "name": "ActivationRequired", + "inputs": [] + }, + { + "type": "error", + "name": "BlobNotFound", + "inputs": [] + }, + { + "type": "error", + "name": "CannotProposeInCurrentBlock", + "inputs": [] + }, + { + "type": "error", + "name": "DeadlineExceeded", + "inputs": [] + }, + { + "type": "error", + "name": "ETH_TRANSFER_FAILED", + "inputs": [] + }, + { + "type": "error", + "name": "EmptyBatch", + "inputs": [] + }, + { + "type": "error", + "name": "FUNC_NOT_IMPLEMENTED", + "inputs": [] + }, + { + "type": "error", + "name": "FirstProposalIdTooLarge", + "inputs": [] + }, + { + "type": "error", + "name": "INVALID_PAUSE_STATUS", + "inputs": [] + }, + { + "type": "error", + "name": "IncorrectProposalCount", + "inputs": [] + }, + { + "type": "error", + "name": "InsufficientBond", + "inputs": [] + }, + { + "type": "error", + "name": "InsufficientETH", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidAddress", + "inputs": [] + }, + { + "type": "error", + "name": "LastProposalAlreadyFinalized", + "inputs": [] + }, + { + "type": "error", + "name": "LastProposalHashMismatch", + "inputs": [] + }, + { + "type": "error", + "name": "LastProposalIdTooLarge", + "inputs": [] + }, + { + "type": "error", + "name": "LengthExceedsUint16", + "inputs": [] + }, + { + "type": "error", + "name": "MustMaintainMinBond", + "inputs": [] + }, + { + "type": "error", + "name": "NoBlobs", + "inputs": [] + }, + { + "type": "error", + "name": "NoBondToWithdraw", + "inputs": [] + }, + { + "type": "error", + "name": "NoWithdrawalRequested", + "inputs": [] + }, + { + "type": "error", + "name": "NotEnoughCapacity", + "inputs": [] + }, + { + "type": "error", + "name": "ParentBlockHashMismatch", + "inputs": [] + }, + { + "type": "error", + "name": "ProverNotWhitelisted", + "inputs": [] + }, + { + "type": "error", + "name": "REENTRANT_CALL", + "inputs": [] + }, + { + "type": "error", + "name": "Surge_CannotProposeDirectlyInLimpMode", + "inputs": [] + }, + { + "type": "error", + "name": "Surge_CannotProveDirectlyInLimpMode", + "inputs": [] + }, + { + "type": "error", + "name": "Surge_CommitmentStateRootsMustDiffer", + "inputs": [] + }, + { + "type": "error", + "name": "Surge_EndBlockNumberMustNotDiffer", + "inputs": [] + }, + { + "type": "error", + "name": "Surge_EndStateRootMustNotDiffer", + "inputs": [] + }, + { + "type": "error", + "name": "Surge_FirstProposalIdMustNotDiffer", + "inputs": [] + }, + { + "type": "error", + "name": "Surge_FirstProposalParentBlockHashMustNotDiffer", + "inputs": [] + }, + { + "type": "error", + "name": "Surge_HeadMustBeFinalizedInLimpMode", + "inputs": [] + }, + { + "type": "error", + "name": "Surge_InsufficientCommitmentsProvided", + "inputs": [] + }, + { + "type": "error", + "name": "Surge_LastProposalHashMustNotDiffer", + "inputs": [] + }, + { + "type": "error", + "name": "Surge_MoreThanOneTransitionProvided", + "inputs": [] + }, + { + "type": "error", + "name": "Surge_NoProposalsToRollback", + "inputs": [] + }, + { + "type": "error", + "name": "Surge_RollbackNotAllowed", + "inputs": [] + }, + { + "type": "error", + "name": "Surge_TransitionBlockhashMustDiffer", + "inputs": [] + }, + { + "type": "error", + "name": "UnprocessedForcedInclusionIsDue", + "inputs": [] + }, + { + "type": "error", + "name": "WithdrawalAlreadyRequested", + "inputs": [] + }, + { + "type": "error", + "name": "ZERO_ADDRESS", + "inputs": [] + }, + { + "type": "error", + "name": "ZERO_VALUE", + "inputs": [] + } + ] +} \ No newline at end of file diff --git a/shasta/src/l1/abi/UserOpsSubmitter.json b/shasta/src/l1/abi/UserOpsSubmitter.json new file mode 100644 index 00000000..0934ef2a --- /dev/null +++ b/shasta/src/l1/abi/UserOpsSubmitter.json @@ -0,0 +1,179 @@ +{ + "abi": [ + { + "type": "constructor", + "inputs": [ + { + "name": "_owner", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "receive", + "stateMutability": "payable" + }, + { + "type": "function", + "name": "executeBatch", + "inputs": [ + { + "name": "_ops", + "type": "tuple[]", + "internalType": "struct UserOpsSubmitter.UserOp[]", + "components": [ + { + "name": "target", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "_signature", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "getDigest", + "inputs": [ + { + "name": "_ops", + "type": "tuple[]", + "internalType": "struct UserOpsSubmitter.UserOp[]", + "components": [ + { + "name": "target", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [ + { + "name": "digest_", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "event", + "name": "BatchExecuted", + "inputs": [ + { + "name": "executor", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "opsCount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OperationExecuted", + "inputs": [ + { + "name": "index", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "target", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "success", + "type": "bool", + "indexed": false, + "internalType": "bool" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "EMPTY_BATCH", + "inputs": [] + }, + { + "type": "error", + "name": "INVALID_OWNER", + "inputs": [] + }, + { + "type": "error", + "name": "INVALID_SIGNATURE", + "inputs": [] + }, + { + "type": "error", + "name": "OPERATION_FAILED", + "inputs": [ + { + "name": "index", + "type": "uint256", + "internalType": "uint256" + } + ] + } + ] +} \ No newline at end of file diff --git a/shasta/src/l1/bindings.rs b/shasta/src/l1/bindings.rs new file mode 100644 index 00000000..464d87cd --- /dev/null +++ b/shasta/src/l1/bindings.rs @@ -0,0 +1,18 @@ +#![allow(clippy::too_many_arguments)] + +use alloy::sol; + +sol!( + #[allow(missing_docs)] + #[sol(rpc)] + SurgeInbox, + "src/l1/abi/SurgeInbox.json" +); + +sol!( + #[allow(missing_docs)] + #[sol(rpc)] + #[derive(Debug)] + Multicall, + "src/l1/abi/Multicall.json" +); diff --git a/shasta/src/l1/config.rs b/shasta/src/l1/config.rs index 536292e5..d064b701 100644 --- a/shasta/src/l1/config.rs +++ b/shasta/src/l1/config.rs @@ -5,10 +5,14 @@ use alloy::primitives::Address; pub struct ContractAddresses { pub shasta_inbox: Address, pub proposer_checker: Address, + pub proposer_multicall: Address, + pub bridge: Address, } pub struct EthereumL1Config { pub shasta_inbox: Address, + pub proposer_multicall: Address, + pub bridge: Address, } impl TryFrom for EthereumL1Config { @@ -17,6 +21,8 @@ impl TryFrom for EthereumL1Config { fn try_from(config: ShastaConfig) -> Result { Ok(EthereumL1Config { shasta_inbox: config.shasta_inbox, + proposer_multicall: config.proposer_multicall, + bridge: config.bridge, }) } } diff --git a/shasta/src/l1/execution_layer.rs b/shasta/src/l1/execution_layer.rs index 5b60692a..94b778f4 100644 --- a/shasta/src/l1/execution_layer.rs +++ b/shasta/src/l1/execution_layer.rs @@ -1,14 +1,25 @@ use super::config::EthereumL1Config; use super::proposal_tx_builder::ProposalTxBuilder; use crate::forced_inclusion::InboxForcedInclusionState; -use crate::l1::config::ContractAddresses; +use crate::node::proposal_manager::proposal::Proposal; +use crate::shared_abi::bindings::{Bridge::MessageSent, IBridge::Message, SignalSent}; +use crate::{l1::config::ContractAddresses, node::proposal_manager::bridge_handler::UserOp}; use alloy::{ - eips::BlockNumberOrTag, + eips::{BlockId, BlockNumberOrTag}, hex::ToHexExt, - primitives::{Address, U256, aliases::U48}, - providers::{DynProvider, Provider}, - rpc::client::BatchRequest, - sol_types::SolCall, + primitives::{Address, FixedBytes, U256, aliases::U48}, + providers::{DynProvider, Provider, ext::DebugApi}, + rpc::{ + client::BatchRequest, + types::{ + TransactionRequest, + trace::geth::{ + GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingCallOptions, + GethDebugTracingOptions, + }, + }, + }, + sol_types::{SolCall, SolEvent}, }; use anyhow::{Context, Error, anyhow}; use common::{ @@ -19,7 +30,7 @@ use common::{ metrics::Metrics, shared::{ alloy_tools, execution_layer::ExecutionLayer as ExecutionLayerCommon, - l2_block_v2::L2BlockV2, transaction_monitor::TransactionMonitor, + transaction_monitor::TransactionMonitor, }, }; use pacaya::l1::{ @@ -45,6 +56,8 @@ pub struct ExecutionLayer { inbox_instance: InboxInstance, operators_cache: OperatorsCache, extra_gas_percentage: u64, + // Surge: For signing the state checkpoints sent as proof with proposal + checkpoint_signer: alloy::signers::local::PrivateKeySigner, } impl ELTrait for ExecutionLayer { @@ -91,6 +104,8 @@ impl ELTrait for ExecutionLayer { let contract_addresses = ContractAddresses { shasta_inbox: specific_config.shasta_inbox, proposer_checker: shasta_config.proposerChecker, + proposer_multicall: specific_config.proposer_multicall, + bridge: specific_config.bridge, }; let operators_cache = @@ -104,6 +119,12 @@ impl ELTrait for ExecutionLayer { inbox_instance, operators_cache, extra_gas_percentage: common_config.extra_gas_percentage, + // Surge: Hard coding the private key for the POC + // (This is the first private key from foundry anvil) + checkpoint_signer: alloy::signers::local::PrivateKeySigner::from_bytes( + &"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + .parse::>()?, + )?, }) } @@ -180,13 +201,17 @@ impl PreconfOperator for ExecutionLayer { impl ExecutionLayer { pub async fn send_proposal_to_l1( &self, - l2_blocks: Vec, - num_forced_inclusion: u16, + batch: Proposal, + tx_hash_notifier: Option>, + tx_result_notifier: Option>, ) -> Result<(), Error> { info!( - "📦 Proposing with {} blocks | num_forced_inclusion: {}", - l2_blocks.len(), - num_forced_inclusion, + "📦 Proposing with {} blocks | num_forced_inclusion: {} | user_ops: {:?} | signal_slots: {:?} | l1_calls: {:?}", + batch.l2_blocks.len(), + batch.num_forced_inclusion, + batch.user_ops, + batch.signal_slots, + batch.l1_calls ); let pending_nonce = self.get_preconfer_nonce_pending().await.map_err(|e| { @@ -195,19 +220,24 @@ impl ExecutionLayer { )) })?; - // Build the transaction asynchronously inside the monitor's spawned task. - // This moves the ~650ms KZG sidecar computation off the hot path. - let tx_builder = ProposalTxBuilder::new( + // Build propose transaction + let builder = ProposalTxBuilder::new( self.provider.clone(), self.extra_gas_percentage, - l2_blocks, - self.common().preconfer_address(), - self.contract_addresses.shasta_inbox, - num_forced_inclusion, + self.checkpoint_signer.clone(), ); + // Surge: This is now a multicall containing user ops and L1 calls + let tx = builder + .build_propose_tx( + batch, + self.common().preconfer_address(), + self.contract_addresses.clone(), + ) + .await?; + self.transaction_monitor - .monitor_new_transaction_with_builder(tx_builder, pending_nonce) + .monitor_new_transaction(tx, pending_nonce, tx_hash_notifier, tx_result_notifier) .await .map_err(|e| Error::msg(format!("Sending proposal to L1 failed: {e}")))?; @@ -452,3 +482,123 @@ impl common::l1::traits::PreconferBondProvider for ExecutionLayer { Ok(U256::from(bond.balance)) } } + +// Surge: L1 EL ops for Bridge Handler + +use alloy::rpc::types::trace::geth::{CallFrame, CallLogFrame}; + +/// Recursively collects all logs from a call frame and its nested subcalls. +/// Logs emitted by nested contract calls are stored within their respective CallFrame objects, +/// not at the top level, so we need to traverse the entire call tree. +fn collect_logs_recursive(frame: &CallFrame) -> Vec { + let mut logs = frame.logs.clone(); + + for subcall in &frame.calls { + logs.extend(collect_logs_recursive(subcall)); + } + + logs +} + +#[allow(async_fn_in_trait)] +pub trait L1BridgeHandlerOps { + // Surge: This can be made to retrieve multiple signal slots + async fn find_message_and_signal_slot( + &self, + user_op: UserOp, + ) -> Result)>, anyhow::Error>; +} + +// Surge: Please beward of these limitations +// - Target contracts are not verified in log checks +impl L1BridgeHandlerOps for ExecutionLayer { + async fn find_message_and_signal_slot( + &self, + user_op_data: UserOp, + ) -> Result)>, anyhow::Error> { + // Create transaction request for simulation, sending calldata directly to the submitter + let tx_request = TransactionRequest::default() + .from(self.common().preconfer_address()) + .to(user_op_data.submitter) + .input(user_op_data.calldata.into()); + + // Configure call tracer with logs and nested calls enabled + let mut tracer_config = serde_json::Map::new(); + tracer_config.insert("withLog".to_string(), serde_json::Value::Bool(true)); + tracer_config.insert("onlyTopCall".to_string(), serde_json::Value::Bool(false)); + + let tracing_options = GethDebugTracingOptions { + tracer: Some(GethDebugTracerType::BuiltInTracer( + GethDebugBuiltInTracerType::CallTracer, + )), + tracer_config: serde_json::Value::Object(tracer_config).into(), + ..Default::default() + }; + + let call_options = GethDebugTracingCallOptions { + tracing_options, + ..Default::default() + }; + + // Execute the trace call simulation + let trace_result = self + .provider + .debug_trace_call( + tx_request, + BlockId::Number(BlockNumberOrTag::Latest), + call_options, + ) + .await + .map_err(|e| anyhow!("Failed to simulate executeBatch on L1: {e}"))?; + + tracing::debug!("Received trace result: {:?}", trace_result); + + let mut message: Option = None; + let mut slot: Option> = None; + + // Look for logs in the trace result and decode MessageSent and SignalSent events + // Note: Logs from nested calls (subcalls) are stored within those nested CallFrame objects, + // so we need to recursively collect all logs from the entire call tree + if let alloy::rpc::types::trace::geth::GethTrace::CallTracer(call_frame) = trace_result { + let all_logs = collect_logs_recursive(&call_frame); + tracing::debug!("Collected {} logs from call trace", all_logs.len()); + + for log in all_logs { + // Check if this is a MessageSent or SignalSent event by matching the topic + if let Some(topics) = &log.topics + && !topics.is_empty() + { + if topics[0] == MessageSent::SIGNATURE_HASH { + // Decode the MessageSent event + let log_data = alloy::primitives::LogData::new_unchecked( + topics.clone(), + log.data.clone().unwrap_or_default(), + ); + let decoded = MessageSent::decode_log_data(&log_data) + .map_err(|e| anyhow!("Failed to decode MessageSent event L1: {e}"))?; + + message = Some(decoded.message); + } else if topics[0] == SignalSent::SIGNATURE_HASH { + // Decode the SignalSent event + let log_data = alloy::primitives::LogData::new_unchecked( + topics.clone(), + log.data.clone().unwrap_or_default(), + ); + let decoded = SignalSent::decode_log_data(&log_data) + .map_err(|e| anyhow!("Failed to decode SignalSent event L1: {e}"))?; + + slot = Some(decoded.slot); + } + } + } + } + + tracing::debug!("{:?} {:?}", message, slot); + + if let (Some(message), Some(slot)) = (message, slot) { + return Ok(Some((message, slot))); + } + + Ok(None) + } +} diff --git a/shasta/src/l1/mod.rs b/shasta/src/l1/mod.rs index a69869fe..7bcc9c57 100644 --- a/shasta/src/l1/mod.rs +++ b/shasta/src/l1/mod.rs @@ -1,3 +1,4 @@ +pub mod bindings; pub mod config; pub mod execution_layer; pub mod proposal_tx_builder; diff --git a/shasta/src/l1/proposal_tx_builder.rs b/shasta/src/l1/proposal_tx_builder.rs index b93cc523..50e0174c 100644 --- a/shasta/src/l1/proposal_tx_builder.rs +++ b/shasta/src/l1/proposal_tx_builder.rs @@ -1,105 +1,64 @@ +use crate::l1::{ + bindings::{IInbox::ProposeInput, LibBlobs::BlobReference, Multicall, SurgeInbox}, + config::ContractAddresses, +}; +use crate::l2::bindings::ICheckpointStore::Checkpoint; +use crate::node::proposal_manager::{ + bridge_handler::{L1Call, UserOp}, + proposal::Proposal, +}; +use crate::shared_abi::bindings::Bridge; use alloy::{ consensus::SidecarBuilder, - eips::eip7594::BlobTransactionSidecarEip7594, - network::{TransactionBuilder, TransactionBuilder7594}, + eips::eip4844::BlobTransactionSidecar, + network::TransactionBuilder4844, primitives::{ - Address, Bytes, + Address, Bytes, U256, aliases::{U24, U48}, }, providers::{DynProvider, Provider}, rpc::types::TransactionRequest, + signers::Signer, + sol_types::SolValue, }; use alloy_json_rpc::RpcError; use anyhow::{Context, Error}; use common::l1::{fees_per_gas::FeesPerGas, tools, transaction_error::TransactionError}; -use common::shared::l2_block_v2::L2BlockV2; -use common::shared::transaction_monitor::TransactionRequestBuilder; -use taiko_bindings::inbox::{IInbox::ProposeInput, Inbox, LibBlobs::BlobReference}; use taiko_protocol::shasta::{ BlobCoder, manifest::{BlockManifest, DerivationSourceManifest}, }; use tracing::{info, warn}; -/// Build the EIP-7594 blob sidecar from L2 blocks. This is a CPU-intensive operation -/// (KZG commitment + cell proof computation). -fn build_sidecar_from_l2_blocks( - l2_blocks: &[L2BlockV2], -) -> Result { - let start = std::time::Instant::now(); - - let mut block_manifests = Vec::with_capacity(l2_blocks.len()); - for l2_block in l2_blocks { - block_manifests.push(BlockManifest { - timestamp: l2_block.timestamp_sec, - coinbase: l2_block.coinbase, - anchor_block_number: l2_block.anchor_block_number, - gas_limit: l2_block.gas_limit_without_anchor, - transactions: l2_block - .prebuilt_tx_list - .tx_list - .iter() - .map(|tx| tx.clone().into()) - .collect(), - }); - } - - let manifest = DerivationSourceManifest { - blocks: block_manifests, - }; - - let manifest_data = manifest - .encode_and_compress() - .map_err(|e| Error::msg(format!("Can't encode and compress manifest: {e}")))?; - - let sidecar_builder: SidecarBuilder = SidecarBuilder::from_slice(&manifest_data); - let sidecar = sidecar_builder - .build_7594() - .map_err(|e| Error::msg(format!("sidecar builder build_7594 failed: {e}")))?; - - info!( - "⏱️ build_sidecar_from_l2_blocks ({} blocks, {} bytes compressed) took {:?}", - l2_blocks.len(), - manifest_data.len(), - start.elapsed() - ); - - Ok(sidecar) -} - pub struct ProposalTxBuilder { provider: DynProvider, extra_gas_percentage: u64, - l2_blocks: Vec, - from: Address, - to: Address, - num_forced_inclusion: u16, + checkpoint_signer: alloy::signers::local::PrivateKeySigner, } impl ProposalTxBuilder { pub fn new( provider: DynProvider, extra_gas_percentage: u64, - l2_blocks: Vec, - from: Address, - to: Address, - num_forced_inclusion: u16, + checkpoint_signer: alloy::signers::local::PrivateKeySigner, ) -> Self { Self { provider, extra_gas_percentage, - l2_blocks, - from, - to, - num_forced_inclusion, + checkpoint_signer, } } - async fn build_propose_tx(&self) -> Result { + #[allow(clippy::too_many_arguments)] + pub async fn build_propose_tx( + &self, + batch: Proposal, + from: Address, + contract_addresses: ContractAddresses, + ) -> Result { let tx_blob = self - .build_propose_blob() - .await - .map_err(|e| Error::msg(format!("build_propose_blob failed: {e}")))?; + .build_propose_blob(batch, from, contract_addresses) + .await?; let tx_blob_gas = match self.provider.estimate_gas(tx_blob.clone()).await { Ok(gas) => gas, Err(e) => { @@ -136,8 +95,123 @@ impl ProposalTxBuilder { Ok(tx_blob) } - async fn build_propose_blob(&self) -> Result { - let sidecar = build_sidecar_from_l2_blocks(&self.l2_blocks)?; + #[allow(clippy::too_many_arguments)] + pub async fn build_propose_blob( + &self, + batch: Proposal, + from: Address, + contract_addresses: ContractAddresses, + ) -> Result { + let mut multicalls: Vec = vec![]; + + // Add user op to multicall + // Note: Only adding the first call, since more calls are not expected for the POC + if !batch.user_ops.is_empty() { + let user_op_call = self.build_user_op_call( + batch + .user_ops + .first() + .ok_or_else(|| anyhow::anyhow!("user_ops is empty despite non-empty check"))? + .clone(), + ); + info!("Added user op to Multicall: {:?}", &user_op_call); + multicalls.push(user_op_call); + } + + // Add the proposal to the multicall + // This must always follow the user ops + let (propose_call, blob_sidecar) = self + .build_propose_call(&batch, contract_addresses.shasta_inbox) + .await?; + info!("Added proposal to Multicall: {:?}", &propose_call); + multicalls.push(propose_call.clone()); + + // Add L1 calls initiated by L2 blocks in the proposal + if !batch.l1_calls.is_empty() { + let l1_call = self.build_l1_call_call( + batch + .l1_calls + .first() + .ok_or_else(|| anyhow::anyhow!("l1_calls is empty despite non-empty check"))? + .clone(), + contract_addresses.bridge, + ); + info!("Added L1 call to Multicall: {:?}", &l1_call); + multicalls.push(l1_call.clone()); + } + + // Build the multicall transaction request + let multicall = Multicall::new(contract_addresses.proposer_multicall, &self.provider); + let call = multicall.multicall(multicalls); + + let tx = TransactionRequest::default() + .to(contract_addresses.proposer_multicall) + .from(from) + .input(call.calldata().clone().into()) + .with_blob_sidecar(blob_sidecar); + + Ok(tx) + } + + // Surge: builds the 161-byte proof data + // [0..96: ABI-encoded checkpoint][96..161: signed checkpoint digest] + async fn build_proof_data(&self, checkpoint: &Checkpoint) -> Result { + let checkpoint_encoded = checkpoint.abi_encode(); + let checkpoint_digest = alloy::primitives::keccak256(&checkpoint_encoded); + let signature = self.checkpoint_signer.sign_hash(&checkpoint_digest).await?; + + let mut signature_bytes = [0_u8; 65]; + signature_bytes[..32].copy_from_slice(signature.r().to_be_bytes::<32>().as_slice()); + signature_bytes[32..64].copy_from_slice(signature.s().to_be_bytes::<32>().as_slice()); + signature_bytes[64] = u8::from(signature.v()) + 27; + + let mut proof_data = Vec::with_capacity(161); + proof_data.extend_from_slice(&checkpoint_encoded); + proof_data.extend_from_slice(&signature_bytes); + Ok(Bytes::from(proof_data)) + } + + // Surge: Multicall builders + + fn build_user_op_call(&self, user_op_data: UserOp) -> Multicall::Call { + Multicall::Call { + target: user_op_data.submitter, + value: U256::ZERO, + data: user_op_data.calldata, + } + } + + async fn build_propose_call( + &self, + batch: &Proposal, + inbox_address: Address, + ) -> Result<(Multicall::Call, BlobTransactionSidecar), anyhow::Error> { + let mut block_manifests = >::with_capacity(batch.l2_blocks.len()); + for l2_block in &batch.l2_blocks { + block_manifests.push(BlockManifest { + timestamp: l2_block.timestamp_sec, + coinbase: l2_block.coinbase, + anchor_block_number: l2_block.anchor_block_number, + gas_limit: l2_block.gas_limit_without_anchor, + transactions: l2_block + .prebuilt_tx_list + .tx_list + .iter() + .map(|tx| tx.clone().into()) + .collect(), + }); + } + + let manifest = DerivationSourceManifest { + blocks: block_manifests, + }; + + let manifest_data = manifest + .encode_and_compress() + .map_err(|e| Error::msg(format!("Can't encode and compress manifest: {e}")))?; + + let sidecar_builder: SidecarBuilder = SidecarBuilder::from_slice(&manifest_data); + let sidecar: BlobTransactionSidecar = sidecar_builder.build()?; // Build the propose input. let input = ProposeInput { @@ -151,34 +225,46 @@ impl ProposalTxBuilder { .context("blobs len try_into")?, offset: U24::ZERO, }, - numForcedInclusions: self.num_forced_inclusion, + numForcedInclusions: batch.num_forced_inclusion, }; - let inbox = Inbox::new(self.to, self.provider.clone()); + let inbox = SurgeInbox::new(inbox_address, self.provider.clone()); let encoded_proposal_input = inbox .encodeProposeInput(input) .call() .await .map_err(|e| Error::msg(format!("inbox encodeProposeInput failed: {e}")))?; - let tx = TransactionRequest::default() - .with_from(self.from) - .with_to(self.to) - .with_blob_sidecar(sidecar) - .with_call(&Inbox::proposeCall { - _lookahead: Bytes::new(), - _data: encoded_proposal_input, - }); + // Surge: using `proposeWithProof(..)` in Surge Inbox + let proof_data = self.build_proof_data(&batch.checkpoint).await?; + let call = inbox.proposeWithProof( + Bytes::new(), + encoded_proposal_input, + proof_data, + batch.signal_slots.clone(), + ); - Ok(tx) + Ok(( + Multicall::Call { + target: inbox_address, + value: U256::ZERO, + data: call.calldata().clone(), + }, + sidecar, + )) } -} -impl TransactionRequestBuilder for ProposalTxBuilder { - async fn build(self) -> Result { - self.build_propose_tx().await.map_err(|e| { - e.downcast::() - .unwrap_or(TransactionError::BuildFailed) - }) + fn build_l1_call_call(&self, l1_call: L1Call, bridge_address: Address) -> Multicall::Call { + let bridge = Bridge::new(bridge_address, &self.provider); + let call = bridge.processMessage(l1_call.message_from_l2, l1_call.signal_slot_proof); + + Multicall::Call { + target: bridge_address, + value: U256::ZERO, + data: call.calldata().clone(), + } } } + +// Note: The POC uses direct build_propose_tx calls with multicall, +// so TransactionRequestBuilder is not implemented here. diff --git a/shasta/src/l2/abi/Anchor.json b/shasta/src/l2/abi/Anchor.json new file mode 100644 index 00000000..d15338fb --- /dev/null +++ b/shasta/src/l2/abi/Anchor.json @@ -0,0 +1,705 @@ +{ + "abi": [ + { + "type": "constructor", + "inputs": [ + { + "name": "_checkpointStore", + "type": "address", + "internalType": "contract ICheckpointStore" + }, + { + "name": "_l1ChainId", + "type": "uint64", + "internalType": "uint64" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "ANCHOR_GAS_LIMIT", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint64", + "internalType": "uint64" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "GOLDEN_TOUCH_ADDRESS", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "acceptOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "anchorV4", + "inputs": [ + { + "name": "_checkpoint", + "type": "tuple", + "internalType": "struct ICheckpointStore.Checkpoint", + "components": [ + { + "name": "blockNumber", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "blockHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "stateRoot", + "type": "bytes32", + "internalType": "bytes32" + } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "anchorV4WithSignalSlots", + "inputs": [ + { + "name": "_checkpoint", + "type": "tuple", + "internalType": "struct ICheckpointStore.Checkpoint", + "components": [ + { + "name": "blockNumber", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "blockHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "stateRoot", + "type": "bytes32", + "internalType": "bytes32" + } + ] + }, + { + "name": "_signalSlots", + "type": "bytes32[]", + "internalType": "bytes32[]" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "anchorV5", + "inputs": [ + { + "name": "_proposalParams", + "type": "tuple", + "internalType": "struct Anchor.ProposalParams", + "components": [ + { + "name": "submissionWindowEnd", + "type": "uint48", + "internalType": "uint48" + } + ] + }, + { + "name": "_blockParams", + "type": "tuple", + "internalType": "struct Anchor.BlockParams", + "components": [ + { + "name": "anchorBlockNumber", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "anchorBlockHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "anchorStateRoot", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "rawTxListHash", + "type": "bytes32", + "internalType": "bytes32" + } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "blockHashes", + "inputs": [ + { + "name": "blockNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "blockHash", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "checkpointStore", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract ICheckpointStore" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getBlockState", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct Anchor.BlockState", + "components": [ + { + "name": "anchorBlockNumber", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "ancestorsHash", + "type": "bytes32", + "internalType": "bytes32" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getPreconfMetadata", + "inputs": [ + { + "name": "_blockNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct Anchor.PreconfMetadata", + "components": [ + { + "name": "anchorBlockNumber", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "submissionWindowEnd", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "parentSubmissionWindowEnd", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "rawTxListHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "parentRawTxListHash", + "type": "bytes32", + "internalType": "bytes32" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "impl", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "inNonReentrant", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "init", + "inputs": [ + { + "name": "_owner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "l1ChainId", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint64", + "internalType": "uint64" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "pause", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "paused", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "pendingOwner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "proxiableUUID", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "resolver", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "unpause", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "upgradeTo", + "inputs": [ + { + "name": "newImplementation", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "upgradeToAndCall", + "inputs": [ + { + "name": "newImplementation", + "type": "address", + "internalType": "address" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "withdraw", + "inputs": [ + { + "name": "_token", + "type": "address", + "internalType": "address" + }, + { + "name": "_to", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "AdminChanged", + "inputs": [ + { + "name": "previousAdmin", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "newAdmin", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Anchored", + "inputs": [ + { + "name": "prevAnchorBlockNumber", + "type": "uint48", + "indexed": false, + "internalType": "uint48" + }, + { + "name": "anchorBlockNumber", + "type": "uint48", + "indexed": false, + "internalType": "uint48" + }, + { + "name": "ancestorsHash", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "BeaconUpgraded", + "inputs": [ + { + "name": "beacon", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Initialized", + "inputs": [ + { + "name": "version", + "type": "uint8", + "indexed": false, + "internalType": "uint8" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferStarted", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Paused", + "inputs": [ + { + "name": "account", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Unpaused", + "inputs": [ + { + "name": "account", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Upgraded", + "inputs": [ + { + "name": "implementation", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Withdrawn", + "inputs": [ + { + "name": "token", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "ACCESS_DENIED", + "inputs": [] + }, + { + "type": "error", + "name": "AncestorsHashMismatch", + "inputs": [] + }, + { + "type": "error", + "name": "ETH_TRANSFER_FAILED", + "inputs": [] + }, + { + "type": "error", + "name": "FUNC_NOT_IMPLEMENTED", + "inputs": [] + }, + { + "type": "error", + "name": "INVALID_PAUSE_STATUS", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidAddress", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidBlockNumber", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidL1ChainId", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidL2ChainId", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidSender", + "inputs": [] + }, + { + "type": "error", + "name": "REENTRANT_CALL", + "inputs": [] + }, + { + "type": "error", + "name": "ZERO_ADDRESS", + "inputs": [] + }, + { + "type": "error", + "name": "ZERO_VALUE", + "inputs": [] + } + ] +} \ No newline at end of file diff --git a/shasta/src/l2/abi/BondManager.json b/shasta/src/l2/abi/BondManager.json new file mode 100644 index 00000000..ae172f03 --- /dev/null +++ b/shasta/src/l2/abi/BondManager.json @@ -0,0 +1 @@ +{"abi":[{"type":"constructor","inputs":[{"name":"_authorized","type":"address","internalType":"address"},{"name":"_bondToken","type":"address","internalType":"address"},{"name":"_minBond","type":"uint256","internalType":"uint256"},{"name":"_withdrawalDelay","type":"uint48","internalType":"uint48"}],"stateMutability":"nonpayable"},{"type":"function","name":"acceptOwnership","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"authorized","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"bond","inputs":[{"name":"account","type":"address","internalType":"address"}],"outputs":[{"name":"balance","type":"uint256","internalType":"uint256"},{"name":"withdrawalRequestedAt","type":"uint48","internalType":"uint48"}],"stateMutability":"view"},{"type":"function","name":"bondToken","inputs":[],"outputs":[{"name":"","type":"address","internalType":"contract IERC20"}],"stateMutability":"view"},{"type":"function","name":"cancelWithdrawal","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"creditBond","inputs":[{"name":"_address","type":"address","internalType":"address"},{"name":"_bond","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"debitBond","inputs":[{"name":"_address","type":"address","internalType":"address"},{"name":"_bond","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"amountDebited_","type":"uint256","internalType":"uint256"}],"stateMutability":"nonpayable"},{"type":"function","name":"deposit","inputs":[{"name":"_amount","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"depositTo","inputs":[{"name":"_recipient","type":"address","internalType":"address"},{"name":"_amount","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"getBondBalance","inputs":[{"name":"_address","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"hasSufficientBond","inputs":[{"name":"_address","type":"address","internalType":"address"},{"name":"_additionalBond","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"impl","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"inNonReentrant","inputs":[],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"init","inputs":[{"name":"_owner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"minBond","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"owner","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"pause","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"paused","inputs":[],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"pendingOwner","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"proxiableUUID","inputs":[],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"renounceOwnership","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"requestWithdrawal","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"resolver","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"transferOwnership","inputs":[{"name":"newOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"unpause","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"upgradeTo","inputs":[{"name":"newImplementation","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"upgradeToAndCall","inputs":[{"name":"newImplementation","type":"address","internalType":"address"},{"name":"data","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"payable"},{"type":"function","name":"withdraw","inputs":[{"name":"_to","type":"address","internalType":"address"},{"name":"_amount","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"withdrawalDelay","inputs":[],"outputs":[{"name":"","type":"uint48","internalType":"uint48"}],"stateMutability":"view"},{"type":"event","name":"AdminChanged","inputs":[{"name":"previousAdmin","type":"address","indexed":false,"internalType":"address"},{"name":"newAdmin","type":"address","indexed":false,"internalType":"address"}],"anonymous":false},{"type":"event","name":"BeaconUpgraded","inputs":[{"name":"beacon","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"BondCredited","inputs":[{"name":"account","type":"address","indexed":true,"internalType":"address"},{"name":"amount","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"BondDebited","inputs":[{"name":"account","type":"address","indexed":true,"internalType":"address"},{"name":"amount","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"BondDeposited","inputs":[{"name":"account","type":"address","indexed":true,"internalType":"address"},{"name":"amount","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"BondDepositedFor","inputs":[{"name":"depositor","type":"address","indexed":true,"internalType":"address"},{"name":"recipient","type":"address","indexed":true,"internalType":"address"},{"name":"amount","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"BondWithdrawn","inputs":[{"name":"account","type":"address","indexed":true,"internalType":"address"},{"name":"amount","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"Initialized","inputs":[{"name":"version","type":"uint8","indexed":false,"internalType":"uint8"}],"anonymous":false},{"type":"event","name":"OwnershipTransferStarted","inputs":[{"name":"previousOwner","type":"address","indexed":true,"internalType":"address"},{"name":"newOwner","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"OwnershipTransferred","inputs":[{"name":"previousOwner","type":"address","indexed":true,"internalType":"address"},{"name":"newOwner","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"Paused","inputs":[{"name":"account","type":"address","indexed":false,"internalType":"address"}],"anonymous":false},{"type":"event","name":"Unpaused","inputs":[{"name":"account","type":"address","indexed":false,"internalType":"address"}],"anonymous":false},{"type":"event","name":"Upgraded","inputs":[{"name":"implementation","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"WithdrawalCancelled","inputs":[{"name":"account","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"WithdrawalRequested","inputs":[{"name":"account","type":"address","indexed":true,"internalType":"address"},{"name":"withdrawableAt","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"error","name":"ACCESS_DENIED","inputs":[]},{"type":"error","name":"FUNC_NOT_IMPLEMENTED","inputs":[]},{"type":"error","name":"INVALID_PAUSE_STATUS","inputs":[]},{"type":"error","name":"InsufficientBond","inputs":[]},{"type":"error","name":"InvalidRecipient","inputs":[]},{"type":"error","name":"MustMaintainMinBond","inputs":[]},{"type":"error","name":"NoBondToWithdraw","inputs":[]},{"type":"error","name":"NoWithdrawalRequested","inputs":[]},{"type":"error","name":"REENTRANT_CALL","inputs":[]},{"type":"error","name":"WithdrawalAlreadyRequested","inputs":[]},{"type":"error","name":"ZERO_ADDRESS","inputs":[]},{"type":"error","name":"ZERO_VALUE","inputs":[]}],"bytecode":{"object":"0x61014060405230608052348015610014575f5ffd5b50604051611f18380380611f188339810160408190526100339161013f565b61003b610068565b6001600160a01b0393841660c0529190921660e0526101009190915265ffffffffffff1661012052610195565b5f54610100900460ff16156100d35760405162461bcd60e51b815260206004820152602760248201527f496e697469616c697a61626c653a20636f6e747261637420697320696e697469604482015266616c697a696e6760c81b606482015260840160405180910390fd5b5f5460ff90811614610122575f805460ff191660ff9081179091556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b565b80516001600160a01b038116811461013a575f5ffd5b919050565b5f5f5f5f60808587031215610152575f5ffd5b61015b85610124565b935061016960208601610124565b925060408501519150606085015165ffffffffffff8116811461018a575f5ffd5b939692955090935050565b60805160a05160c05160e0516101005161012051611ce16102375f395f818161048101528181610d700152610e6201525f81816103ea01528181610baa0152610ea301525f818161050901528181610c0f01528181610f49015261142f01525f81816103480152818161085e0152610c8201525f6101cc01525f818161079e015281816107de015281816109460152818161098601526109fd0152611ce15ff3fe6080604052600436106101ba575f3560e01c806379ba5097116100f2578063b6b55f2511610092578063e30c397811610062578063e30c39781461053f578063f2fde38b1461055c578063f3fef3a31461057b578063ffaad6a51461059a575f5ffd5b8063b6b55f25146104ba578063be32d1f4146104d9578063c28f4392146104f8578063dbaf21451461052b575f5ffd5b80638abf6077116100cd5780638abf6077146104205780638da5cb5b14610434578063a116e48614610451578063a7ab696114610470575f5ffd5b806379ba5097146103c5578063831518b7146103d95780638456cb591461040c575f5ffd5b8063391396de1161015d5780634f1ef286116101385780634f1ef2861461036a57806352d1902d1461037d5780635c975abb14610391578063715018a6146103b1575f5ffd5b8063391396de146103045780633f4ba83a14610323578063456cb7c614610337575f5ffd5b8063247ce85b11610198578063247ce85b1461023e5780633075db561461029457806333613cbe146102b85780633659cfe6146102e5575f5ffd5b806304f3bcec146101be57806319ab453c14610209578063226112801461022a575b5f5ffd5b3480156101c9575f5ffd5b507f00000000000000000000000000000000000000000000000000000000000000005b6040516001600160a01b0390911681526020015b60405180910390f35b348015610214575f5ffd5b50610228610223366004611954565b6105b9565b005b348015610235575f5ffd5b506102286106cb565b348015610249575f5ffd5b50610278610258366004611954565b60fb6020525f90815260409020805460019091015465ffffffffffff1682565b6040805192835265ffffffffffff909116602083015201610200565b34801561029f575f5ffd5b506102a861075d565b6040519015158152602001610200565b3480156102c3575f5ffd5b506102d76102d2366004611954565b610775565b604051908152602001610200565b3480156102f0575f5ffd5b506102286102ff366004611954565b610794565b34801561030f575f5ffd5b506102d761031e36600461196d565b61085b565b34801561032e575f5ffd5b506102286108e3565b348015610342575f5ffd5b506101ec7f000000000000000000000000000000000000000000000000000000000000000081565b6102286103783660046119a9565b61093c565b348015610388575f5ffd5b506102d76109f1565b34801561039c575f5ffd5b506102a860c954610100900460ff1660021490565b3480156103bc575f5ffd5b50610228610aa2565b3480156103d0575f5ffd5b50610228610ab3565b3480156103e4575f5ffd5b506102d77f000000000000000000000000000000000000000000000000000000000000000081565b348015610417575f5ffd5b50610228610b2a565b34801561042b575f5ffd5b506101ec610b7f565b34801561043f575f5ffd5b506033546001600160a01b03166101ec565b34801561045c575f5ffd5b506102a861046b36600461196d565b610b8d565b34801561047b575f5ffd5b506104a37f000000000000000000000000000000000000000000000000000000000000000081565b60405165ffffffffffff9091168152602001610200565b3480156104c5575f5ffd5b506102286104d4366004611a6d565b610bf0565b3480156104e4575f5ffd5b506102286104f336600461196d565b610c80565b348015610503575f5ffd5b506101ec7f000000000000000000000000000000000000000000000000000000000000000081565b348015610536575f5ffd5b50610228610cb9565b34801561054a575f5ffd5b506065546001600160a01b03166101ec565b348015610567575f5ffd5b50610228610576366004611954565b610db2565b348015610586575f5ffd5b5061022861059536600461196d565b610e23565b3480156105a5575f5ffd5b506102286105b436600461196d565b610f03565b5f54610100900460ff16158080156105d757505f54600160ff909116105b806105f05750303b1580156105f057505f5460ff166001145b6106585760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084015b60405180910390fd5b5f805460ff191660011790558015610679575f805461ff0019166101001790555b61068282610fc5565b80156106c7575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b6106d3611023565b6106dd6002611052565b335f90815260fb60205260409020600181015465ffffffffffff16610715576040516387bdc6a960e01b815260040160405180910390fd5b60018101805465ffffffffffff1916905560405133907fc51fdb96728de385ec7859819e3997bc618362ef0dbca0ad051d856866cda3db905f90a25061075b6001611052565b565b5f600261076c60c95460ff1690565b60ff1614905090565b6001600160a01b0381165f90815260fb60205260408120545b92915050565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001630036107dc5760405162461bcd60e51b815260040161064f90611a84565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031661080e611068565b6001600160a01b0316146108345760405162461bcd60e51b815260040161064f90611ad0565b61083d81611083565b604080515f808252602082019092526108589183919061108b565b50565b5f7f0000000000000000000000000000000000000000000000000000000000000000610886816111f5565b610890848461121e565b915081156108dc57836001600160a01b03167f85f32beeaff2d0019a8d196f06790c9a652191759c46643311344fd38920423c836040516108d391815260200190565b60405180910390a25b5092915050565b6108eb611261565b6108ff60c9805461ff001916610100179055565b6040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a161075b335f611292565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001630036109845760405162461bcd60e51b815260040161064f90611a84565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166109b6611068565b6001600160a01b0316146109dc5760405162461bcd60e51b815260040161064f90611ad0565b6109e582611083565b6106c78282600161108b565b5f306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610a905760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c0000000000000000606482015260840161064f565b505f516020611c655f395f51905f5290565b610aaa611296565b61075b5f6112f0565b60655433906001600160a01b03168114610b215760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b606482015260840161064f565b610858816112f0565b610b32611309565b60c9805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a161075b336001611292565b5f610b88611068565b905090565b6001600160a01b0382165f90815260fb60205260408120610bce837f0000000000000000000000000000000000000000000000000000000000000000611b30565b815410801590610be85750600181015465ffffffffffff16155b949350505050565b610bf8611023565b610c026002611052565b610c376001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633308461133b565b610c4133826113ac565b60405181815233907f8ed8c6869618197b68315ade66e75ed3906c97b111fa3ab81e5760046825c7db9060200160405180910390a26108586001611052565b7f0000000000000000000000000000000000000000000000000000000000000000610caa816111f5565b610cb483836113ac565b505050565b610cc1611023565b610ccb6002611052565b335f90815260fb602052604090208054610cf857604051634555262b60e01b815260040160405180910390fd5b600181015465ffffffffffff1615610d235760405163fb52063b60e01b815260040160405180910390fd5b60018101805465ffffffffffff4281811665ffffffffffff199093169290921790925533917fe670e4e82118d22a1f9ee18920455ebc958bae26a90a05d31d3378788b1b0e4491610d96917f00000000000000000000000000000000000000000000000000000000000000001690611b30565b60405190815260200160405180910390a25061075b6001611052565b610dba611296565b606580546001600160a01b0383166001600160a01b03199091168117909155610deb6033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b610e2b611023565b610e356002611052565b335f90815260fb60205260409020600181015465ffffffffffff161580610e9a57506001810154610e8f907f00000000000000000000000000000000000000000000000000000000000000009065ffffffffffff16611b43565b65ffffffffffff1642105b15610eed5780547f000000000000000000000000000000000000000000000000000000000000000090610ece908490611b61565b1015610eed576040516321f68cc160e21b815260040160405180910390fd5b610ef8338484611415565b506106c76001611052565b610f0b611023565b610f156002611052565b6001600160a01b038216610f3c57604051634e46966960e11b815260040160405180910390fd5b610f716001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633308461133b565b610f7b82826113ac565b6040518181526001600160a01b0383169033907fd723e7d201efb4985d2e17282a1a09e756601f0b4a724d36ea60913ecae4ae9a9060200160405180910390a36106c76001611052565b5f54610100900460ff16610feb5760405162461bcd60e51b815260040161064f90611b74565b610ff361149f565b6110116001600160a01b0382161561100b57816112f0565b336112f0565b5060c9805461ff001916610100179055565b600261103160c95460ff1690565b60ff160361075b5760405163dfc60d8560e01b815260040160405180910390fd5b60c9805460ff191660ff92909216919091179055565b5f516020611c655f395f51905f52546001600160a01b031690565b610858611296565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff16156110be57610cb4836114c5565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611118575060408051601f3d908101601f1916820190925261111591810190611bbf565b60015b61117b5760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b606482015260840161064f565b5f516020611c655f395f51905f5281146111e95760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b606482015260840161064f565b50610cb4838383611560565b336001600160a01b03821614610858576040516395383ea160e01b815260040160405180910390fd5b6001600160a01b0382165f90815260fb60205260408120805483106112495780545f825591506108dc565b8054839250611259908390611b61565b905592915050565b61127560c954610100900460ff1660021490565b61075b5760405163bae6e2a960e01b815260040160405180910390fd5b6106c75b6033546001600160a01b0316331461075b5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161064f565b606580546001600160a01b031916905561085881611584565b61131d60c954610100900460ff1660021490565b1561075b5760405163bae6e2a960e01b815260040160405180910390fd5b6040516001600160a01b03808516602483015283166044820152606481018290526113a69085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b0319909316929092179091526115d5565b50505050565b6001600160a01b0382165f90815260fb6020526040902080546113d0908390611b30565b81556040518281526001600160a01b038416907f6de6fe586196fa05b73b973026c5fda3968a2933989bff3a0b6bd57644fab6069060200160405180910390a2505050565b5f611420848361121e565b90506114566001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001684836116a8565b836001600160a01b03167f0d41118e36df44efb77a471fc49fb9c0be0406d802ef95520e9fbf606e65b4558260405161149191815260200190565b60405180910390a250505050565b5f54610100900460ff1661075b5760405162461bcd60e51b815260040161064f90611b74565b6001600160a01b0381163b6115325760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161064f565b5f516020611c655f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b611569836116d8565b5f825111806115755750805b15610cb4576113a68383611717565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f611629826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166117439092919063ffffffff16565b905080515f14806116495750808060200190518101906116499190611bd6565b610cb45760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b606482015260840161064f565b6040516001600160a01b038316602482015260448101829052610cb490849063a9059cbb60e01b9060640161136f565b6116e1816114c5565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b606061173c8383604051806060016040528060278152602001611c8560279139611751565b9392505050565b6060610be884845f856117c5565b60605f5f856001600160a01b03168560405161176d9190611c17565b5f60405180830381855af49150503d805f81146117a5576040519150601f19603f3d011682016040523d82523d5f602084013e6117aa565b606091505b50915091506117bb8683838761189c565b9695505050505050565b6060824710156118265760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b606482015260840161064f565b5f5f866001600160a01b031685876040516118419190611c17565b5f6040518083038185875af1925050503d805f811461187b576040519150601f19603f3d011682016040523d82523d5f602084013e611880565b606091505b50915091506118918783838761189c565b979650505050505050565b6060831561190a5782515f03611903576001600160a01b0385163b6119035760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161064f565b5081610be8565b610be8838381511561191f5781518083602001fd5b8060405162461bcd60e51b815260040161064f9190611c32565b80356001600160a01b038116811461194f575f5ffd5b919050565b5f60208284031215611964575f5ffd5b61173c82611939565b5f5f6040838503121561197e575f5ffd5b61198783611939565b946020939093013593505050565b634e487b7160e01b5f52604160045260245ffd5b5f5f604083850312156119ba575f5ffd5b6119c383611939565b9150602083013567ffffffffffffffff8111156119de575f5ffd5b8301601f810185136119ee575f5ffd5b803567ffffffffffffffff811115611a0857611a08611995565b604051601f8201601f19908116603f0116810167ffffffffffffffff81118282101715611a3757611a37611995565b604052818152828201602001871015611a4e575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f60208284031215611a7d575f5ffd5b5035919050565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b634e487b7160e01b5f52601160045260245ffd5b8082018082111561078e5761078e611b1c565b65ffffffffffff818116838216019081111561078e5761078e611b1c565b8181038181111561078e5761078e611b1c565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f60208284031215611bcf575f5ffd5b5051919050565b5f60208284031215611be6575f5ffd5b8151801515811461173c575f5ffd5b5f5b83811015611c0f578181015183820152602001611bf7565b50505f910152565b5f8251611c28818460208701611bf5565b9190910192915050565b602081525f8251806020840152611c50816040850160208701611bf5565b601f01601f1916919091016040019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212208631ea18b120dd608cae3a5d1f20512dffdc4afab9092db6c85c4d2fb0413eb864736f6c634300081e0033","sourceMap":"498:7572:4:-:0;;;1088:4:84;1045:48;;2108:287:4;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4375:22:12;:20;:22::i;:::-;-1:-1:-1;;;;;2252:24:4;;;;;2286:30;;;;;;2326:18;;;;;2354:34;;;;498:7572;;5939:280:48;6007:13;;;;;;;6006:14;5998:66;;;;-1:-1:-1;;;5998:66:48;;919:2:150;5998:66:48;;;901:21:150;958:2;938:18;;;931:30;997:34;977:18;;;970:62;-1:-1:-1;;;1048:18:150;;;1041:37;1095:19;;5998:66:48;;;;;;;;6078:12;;6094:15;6078:12;;;:31;6074:139;;6125:12;:30;;-1:-1:-1;;6125:30:48;6140:15;6125:30;;;;;;6174:28;;1267:36:150;;;6174:28:48;;1255:2:150;1240:18;6174:28:48;;;;;;;6074:139;5939:280::o;14:177:150:-;93:13;;-1:-1:-1;;;;;135:31:150;;125:42;;115:70;;181:1;178;171:12;115:70;14:177;;;:::o;196:516::-;292:6;300;308;316;369:3;357:9;348:7;344:23;340:33;337:53;;;386:1;383;376:12;337:53;409:40;439:9;409:40;:::i;:::-;399:50;;468:49;513:2;502:9;498:18;468:49;:::i;:::-;458:59;;557:2;546:9;542:18;536:25;526:35;;604:2;593:9;589:18;583:25;648:14;641:5;637:26;630:5;627:37;617:65;;678:1;675;668:12;617:65;196:516;;;;-1:-1:-1;196:516:150;;-1:-1:-1;;196:516:150:o;1125:184::-;498:7572:4;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x6080604052600436106101ba575f3560e01c806379ba5097116100f2578063b6b55f2511610092578063e30c397811610062578063e30c39781461053f578063f2fde38b1461055c578063f3fef3a31461057b578063ffaad6a51461059a575f5ffd5b8063b6b55f25146104ba578063be32d1f4146104d9578063c28f4392146104f8578063dbaf21451461052b575f5ffd5b80638abf6077116100cd5780638abf6077146104205780638da5cb5b14610434578063a116e48614610451578063a7ab696114610470575f5ffd5b806379ba5097146103c5578063831518b7146103d95780638456cb591461040c575f5ffd5b8063391396de1161015d5780634f1ef286116101385780634f1ef2861461036a57806352d1902d1461037d5780635c975abb14610391578063715018a6146103b1575f5ffd5b8063391396de146103045780633f4ba83a14610323578063456cb7c614610337575f5ffd5b8063247ce85b11610198578063247ce85b1461023e5780633075db561461029457806333613cbe146102b85780633659cfe6146102e5575f5ffd5b806304f3bcec146101be57806319ab453c14610209578063226112801461022a575b5f5ffd5b3480156101c9575f5ffd5b507f00000000000000000000000000000000000000000000000000000000000000005b6040516001600160a01b0390911681526020015b60405180910390f35b348015610214575f5ffd5b50610228610223366004611954565b6105b9565b005b348015610235575f5ffd5b506102286106cb565b348015610249575f5ffd5b50610278610258366004611954565b60fb6020525f90815260409020805460019091015465ffffffffffff1682565b6040805192835265ffffffffffff909116602083015201610200565b34801561029f575f5ffd5b506102a861075d565b6040519015158152602001610200565b3480156102c3575f5ffd5b506102d76102d2366004611954565b610775565b604051908152602001610200565b3480156102f0575f5ffd5b506102286102ff366004611954565b610794565b34801561030f575f5ffd5b506102d761031e36600461196d565b61085b565b34801561032e575f5ffd5b506102286108e3565b348015610342575f5ffd5b506101ec7f000000000000000000000000000000000000000000000000000000000000000081565b6102286103783660046119a9565b61093c565b348015610388575f5ffd5b506102d76109f1565b34801561039c575f5ffd5b506102a860c954610100900460ff1660021490565b3480156103bc575f5ffd5b50610228610aa2565b3480156103d0575f5ffd5b50610228610ab3565b3480156103e4575f5ffd5b506102d77f000000000000000000000000000000000000000000000000000000000000000081565b348015610417575f5ffd5b50610228610b2a565b34801561042b575f5ffd5b506101ec610b7f565b34801561043f575f5ffd5b506033546001600160a01b03166101ec565b34801561045c575f5ffd5b506102a861046b36600461196d565b610b8d565b34801561047b575f5ffd5b506104a37f000000000000000000000000000000000000000000000000000000000000000081565b60405165ffffffffffff9091168152602001610200565b3480156104c5575f5ffd5b506102286104d4366004611a6d565b610bf0565b3480156104e4575f5ffd5b506102286104f336600461196d565b610c80565b348015610503575f5ffd5b506101ec7f000000000000000000000000000000000000000000000000000000000000000081565b348015610536575f5ffd5b50610228610cb9565b34801561054a575f5ffd5b506065546001600160a01b03166101ec565b348015610567575f5ffd5b50610228610576366004611954565b610db2565b348015610586575f5ffd5b5061022861059536600461196d565b610e23565b3480156105a5575f5ffd5b506102286105b436600461196d565b610f03565b5f54610100900460ff16158080156105d757505f54600160ff909116105b806105f05750303b1580156105f057505f5460ff166001145b6106585760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084015b60405180910390fd5b5f805460ff191660011790558015610679575f805461ff0019166101001790555b61068282610fc5565b80156106c7575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b6106d3611023565b6106dd6002611052565b335f90815260fb60205260409020600181015465ffffffffffff16610715576040516387bdc6a960e01b815260040160405180910390fd5b60018101805465ffffffffffff1916905560405133907fc51fdb96728de385ec7859819e3997bc618362ef0dbca0ad051d856866cda3db905f90a25061075b6001611052565b565b5f600261076c60c95460ff1690565b60ff1614905090565b6001600160a01b0381165f90815260fb60205260408120545b92915050565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001630036107dc5760405162461bcd60e51b815260040161064f90611a84565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031661080e611068565b6001600160a01b0316146108345760405162461bcd60e51b815260040161064f90611ad0565b61083d81611083565b604080515f808252602082019092526108589183919061108b565b50565b5f7f0000000000000000000000000000000000000000000000000000000000000000610886816111f5565b610890848461121e565b915081156108dc57836001600160a01b03167f85f32beeaff2d0019a8d196f06790c9a652191759c46643311344fd38920423c836040516108d391815260200190565b60405180910390a25b5092915050565b6108eb611261565b6108ff60c9805461ff001916610100179055565b6040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a161075b335f611292565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001630036109845760405162461bcd60e51b815260040161064f90611a84565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166109b6611068565b6001600160a01b0316146109dc5760405162461bcd60e51b815260040161064f90611ad0565b6109e582611083565b6106c78282600161108b565b5f306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610a905760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c0000000000000000606482015260840161064f565b505f516020611c655f395f51905f5290565b610aaa611296565b61075b5f6112f0565b60655433906001600160a01b03168114610b215760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b606482015260840161064f565b610858816112f0565b610b32611309565b60c9805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a161075b336001611292565b5f610b88611068565b905090565b6001600160a01b0382165f90815260fb60205260408120610bce837f0000000000000000000000000000000000000000000000000000000000000000611b30565b815410801590610be85750600181015465ffffffffffff16155b949350505050565b610bf8611023565b610c026002611052565b610c376001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633308461133b565b610c4133826113ac565b60405181815233907f8ed8c6869618197b68315ade66e75ed3906c97b111fa3ab81e5760046825c7db9060200160405180910390a26108586001611052565b7f0000000000000000000000000000000000000000000000000000000000000000610caa816111f5565b610cb483836113ac565b505050565b610cc1611023565b610ccb6002611052565b335f90815260fb602052604090208054610cf857604051634555262b60e01b815260040160405180910390fd5b600181015465ffffffffffff1615610d235760405163fb52063b60e01b815260040160405180910390fd5b60018101805465ffffffffffff4281811665ffffffffffff199093169290921790925533917fe670e4e82118d22a1f9ee18920455ebc958bae26a90a05d31d3378788b1b0e4491610d96917f00000000000000000000000000000000000000000000000000000000000000001690611b30565b60405190815260200160405180910390a25061075b6001611052565b610dba611296565b606580546001600160a01b0383166001600160a01b03199091168117909155610deb6033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b610e2b611023565b610e356002611052565b335f90815260fb60205260409020600181015465ffffffffffff161580610e9a57506001810154610e8f907f00000000000000000000000000000000000000000000000000000000000000009065ffffffffffff16611b43565b65ffffffffffff1642105b15610eed5780547f000000000000000000000000000000000000000000000000000000000000000090610ece908490611b61565b1015610eed576040516321f68cc160e21b815260040160405180910390fd5b610ef8338484611415565b506106c76001611052565b610f0b611023565b610f156002611052565b6001600160a01b038216610f3c57604051634e46966960e11b815260040160405180910390fd5b610f716001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633308461133b565b610f7b82826113ac565b6040518181526001600160a01b0383169033907fd723e7d201efb4985d2e17282a1a09e756601f0b4a724d36ea60913ecae4ae9a9060200160405180910390a36106c76001611052565b5f54610100900460ff16610feb5760405162461bcd60e51b815260040161064f90611b74565b610ff361149f565b6110116001600160a01b0382161561100b57816112f0565b336112f0565b5060c9805461ff001916610100179055565b600261103160c95460ff1690565b60ff160361075b5760405163dfc60d8560e01b815260040160405180910390fd5b60c9805460ff191660ff92909216919091179055565b5f516020611c655f395f51905f52546001600160a01b031690565b610858611296565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff16156110be57610cb4836114c5565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611118575060408051601f3d908101601f1916820190925261111591810190611bbf565b60015b61117b5760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b606482015260840161064f565b5f516020611c655f395f51905f5281146111e95760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b606482015260840161064f565b50610cb4838383611560565b336001600160a01b03821614610858576040516395383ea160e01b815260040160405180910390fd5b6001600160a01b0382165f90815260fb60205260408120805483106112495780545f825591506108dc565b8054839250611259908390611b61565b905592915050565b61127560c954610100900460ff1660021490565b61075b5760405163bae6e2a960e01b815260040160405180910390fd5b6106c75b6033546001600160a01b0316331461075b5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161064f565b606580546001600160a01b031916905561085881611584565b61131d60c954610100900460ff1660021490565b1561075b5760405163bae6e2a960e01b815260040160405180910390fd5b6040516001600160a01b03808516602483015283166044820152606481018290526113a69085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b0319909316929092179091526115d5565b50505050565b6001600160a01b0382165f90815260fb6020526040902080546113d0908390611b30565b81556040518281526001600160a01b038416907f6de6fe586196fa05b73b973026c5fda3968a2933989bff3a0b6bd57644fab6069060200160405180910390a2505050565b5f611420848361121e565b90506114566001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001684836116a8565b836001600160a01b03167f0d41118e36df44efb77a471fc49fb9c0be0406d802ef95520e9fbf606e65b4558260405161149191815260200190565b60405180910390a250505050565b5f54610100900460ff1661075b5760405162461bcd60e51b815260040161064f90611b74565b6001600160a01b0381163b6115325760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161064f565b5f516020611c655f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b611569836116d8565b5f825111806115755750805b15610cb4576113a68383611717565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f611629826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166117439092919063ffffffff16565b905080515f14806116495750808060200190518101906116499190611bd6565b610cb45760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b606482015260840161064f565b6040516001600160a01b038316602482015260448101829052610cb490849063a9059cbb60e01b9060640161136f565b6116e1816114c5565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b606061173c8383604051806060016040528060278152602001611c8560279139611751565b9392505050565b6060610be884845f856117c5565b60605f5f856001600160a01b03168560405161176d9190611c17565b5f60405180830381855af49150503d805f81146117a5576040519150601f19603f3d011682016040523d82523d5f602084013e6117aa565b606091505b50915091506117bb8683838761189c565b9695505050505050565b6060824710156118265760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b606482015260840161064f565b5f5f866001600160a01b031685876040516118419190611c17565b5f6040518083038185875af1925050503d805f811461187b576040519150601f19603f3d011682016040523d82523d5f602084013e611880565b606091505b50915091506118918783838761189c565b979650505050505050565b6060831561190a5782515f03611903576001600160a01b0385163b6119035760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161064f565b5081610be8565b610be8838381511561191f5781518083602001fd5b8060405162461bcd60e51b815260040161064f9190611c32565b80356001600160a01b038116811461194f575f5ffd5b919050565b5f60208284031215611964575f5ffd5b61173c82611939565b5f5f6040838503121561197e575f5ffd5b61198783611939565b946020939093013593505050565b634e487b7160e01b5f52604160045260245ffd5b5f5f604083850312156119ba575f5ffd5b6119c383611939565b9150602083013567ffffffffffffffff8111156119de575f5ffd5b8301601f810185136119ee575f5ffd5b803567ffffffffffffffff811115611a0857611a08611995565b604051601f8201601f19908116603f0116810167ffffffffffffffff81118282101715611a3757611a37611995565b604052818152828201602001871015611a4e575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f60208284031215611a7d575f5ffd5b5035919050565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b634e487b7160e01b5f52601160045260245ffd5b8082018082111561078e5761078e611b1c565b65ffffffffffff818116838216019081111561078e5761078e611b1c565b8181038181111561078e5761078e611b1c565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f60208284031215611bcf575f5ffd5b5051919050565b5f60208284031215611be6575f5ffd5b8151801515811461173c575f5ffd5b5f5b83811015611c0f578181015183820152602001611bf7565b50505f910152565b5f8251611c28818460208701611bf5565b9190910192915050565b602081525f8251806020840152611c50816040850160208701611bf5565b601f01601f1916919091016040019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212208631ea18b120dd608cae3a5d1f20512dffdc4afab9092db6c85c4d2fb0413eb864736f6c634300081e0033","sourceMap":"498:7572:4:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5694:92:12;;;;;;;;;;-1:-1:-1;5769:10:12;5694:92;;;-1:-1:-1;;;;;178:32:150;;;160:51;;148:2;133:18;5694:92:12;;;;;;;;2503::4;;;;;;;;;;-1:-1:-1;2503:92:4;;;;;:::i;:::-;;:::i;:::-;;4893:267;;;;;;;;;;;;;:::i;1506:49::-;;;;;;;;;;-1:-1:-1;1506:49:4;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;763:25:150;;;836:14;824:27;;;819:2;804:18;;797:55;736:18;1506:49:4;591:267:150;5484:104:12;;;;;;;;;;;;;:::i;:::-;;;1028:14:150;;1021:22;1003:41;;991:2;976:18;5484:104:12;863:187:150;3335:123:4;;;;;;;;;;-1:-1:-1;3335:123:4;;;;;:::i;:::-;;:::i;:::-;;;1201:25:150;;;1189:2;1174:18;3335:123:4;1055:177:150;3143:195:84;;;;;;;;;;-1:-1:-1;3143:195:84;;;;;:::i;:::-;;:::i;2803:326:4:-;;;;;;;;;;-1:-1:-1;2803:326:4;;;;;:::i;:::-;;:::i;4911:245:12:-;;;;;;;;;;;;;:::i;858:35:4:-;;;;;;;;;;;;;;;3657:220:84;;;;;;:::i;:::-;;:::i;2762:131::-;;;;;;;;;;;;;:::i;5384:94:12:-;;;;;;;;;;;;5454:8;;;;;:17;:8;650:1;5454:17;;5384:94;2085:101:43;;;;;;;;;;;;;:::i;2031:212:42:-;;;;;;;;;;;;;:::i;1020:32:4:-;;;;;;;;;;;;;;;4625:241:12;;;;;;;;;;;;;:::i;5162:90::-;;;;;;;;;;;;;:::i;1462:85:43:-;;;;;;;;;;-1:-1:-1;1534:6:43;;-1:-1:-1;;;;;1534:6:43;1462:85;;4132:296:4;;;;;;;;;;-1:-1:-1;4132:296:4;;;;;:::i;:::-;;:::i;1421:39::-;;;;;;;;;;;;;;;;;;3053:14:150;3041:27;;;3023:46;;3011:2;2996:18;1421:39:4;2879:196:150;3497:228:4;;;;;;;;;;-1:-1:-1;3497:228:4;;;;;:::i;:::-;;:::i;3168:128::-;;;;;;;;;;-1:-1:-1;3168:128:4;;;;;:::i;:::-;;:::i;942:33::-;;;;;;;;;;;;;;;4467:387;;;;;;;;;;;;;:::i;1144:99:42:-;;;;;;;;;;-1:-1:-1;1223:13:42;;-1:-1:-1;;;;;1223:13:42;1144:99;;1436:178;;;;;;;;;;-1:-1:-1;1436:178:42;;;;;:::i;:::-;;:::i;5199:534:4:-;;;;;;;;;;-1:-1:-1;5199:534:4;;;;;:::i;:::-;;:::i;3764:329::-;;;;;;;;;;-1:-1:-1;3764:329:4;;;;;:::i;:::-;;:::i;2503:92::-;3279:19:48;3302:13;;;;;;3301:14;;3347:34;;;;-1:-1:-1;3365:12:48;;3380:1;3365:12;;;;:16;3347:34;3346:108;;;-1:-1:-1;3426:4:48;1713:19:64;:23;;;3387:66:48;;-1:-1:-1;3436:12:48;;;;;:17;3387:66;3325:201;;;;-1:-1:-1;;;3325:201:48;;3737:2:150;3325:201:48;;;3719:21:150;3776:2;3756:18;;;3749:30;3815:34;3795:18;;;3788:62;-1:-1:-1;;;3866:18:150;;;3859:44;3920:19;;3325:201:48;;;;;;;;;3536:12;:16;;-1:-1:-1;;3536:16:48;3551:1;3536:16;;;3562:65;;;;3596:13;:20;;-1:-1:-1;;3596:20:48;;;;;3562:65;2564:24:4::1;2581:6;2564:16;:24::i;:::-;3651:14:48::0;3647:99;;;3697:5;3681:21;;-1:-1:-1;;3681:21:48;;;3721:14;;-1:-1:-1;4102:36:150;;3721:14:48;;4090:2:150;4075:18;3721:14:48;;;;;;;3647:99;3269:483;2503:92:4;:::o;4893:267::-;2345:18:12;:16;:18::i;:::-;2373:24;650:1;2373:17;:24::i;:::-;4979:10:4::1;4953:18;4974:16:::0;;;:4:::1;:16;::::0;;;;5008:27:::1;::::0;::::1;::::0;::::1;;5000:65;;;;-1:-1:-1::0;;;5000:65:4::1;;;;;;;;;;;;5076:27;::::0;::::1;:31:::0;;-1:-1:-1;;5076:31:4::1;::::0;;5122::::1;::::0;5142:10:::1;::::0;5122:31:::1;::::0;5106:1:::1;::::0;5122:31:::1;4943:217;2418:25:12::0;611:1;2418:17;:25::i;:::-;4893:267:4:o;5484:104:12:-;5531:4;650:1;5554:18;6882:9;;;;;6786:112;5554:18;:27;;;5547:34;;5484:104;:::o;3335:123:4:-;-1:-1:-1;;;;;7683:14:4;;3400:7;7683:14;;;:4;:14;;;;;:22;3426:25;3419:32;3335:123;-1:-1:-1;;3335:123:4:o;3143:195:84:-;-1:-1:-1;;;;;1654:6:84;1637:23;1645:4;1637:23;1629:80;;;;-1:-1:-1;;;1629:80:84;;;;;;;:::i;:::-;1751:6;-1:-1:-1;;;;;1727:30:84;:20;:18;:20::i;:::-;-1:-1:-1;;;;;1727:30:84;;1719:87;;;;-1:-1:-1;;;1719:87:84;;;;;;;:::i;:::-;3224:36:::1;3242:17;3224;:36::i;:::-;3311:12;::::0;;3321:1:::1;3311:12:::0;;;::::1;::::0;::::1;::::0;;;3270:61:::1;::::0;3292:17;;3311:12;3270:21:::1;:61::i;:::-;3143:195:::0;:::o;2803:326:4:-;2940:22;2911:10;3925:17:12;3936:5;3925:10;:17::i;:::-;2995:27:4::1;3006:8;3016:5;2995:10;:27::i;:::-;2978:44:::0;-1:-1:-1;3036:18:4;;3032:91:::1;;3087:8;-1:-1:-1::0;;;;;3075:37:4::1;;3097:14;3075:37;;;;1201:25:150::0;;1189:2;1174:18;;1055:177;3075:37:4::1;;;;;;;;3032:91;2803:326:::0;;;;;:::o;4911:245:12:-;2575:14;:12;:14::i;:::-;4958:10:::1;6435:8:::0;:17;;-1:-1:-1;;6435:17:12;;;;;6388:71;4958:10:::1;4983:20;::::0;4992:10:::1;160:51:150::0;;4983:20:12::1;::::0;148:2:150;133:18;4983:20:12::1;;;;;;;5115:34;5131:10;5143:5;5115:15;:34::i;3657:220:84:-:0;-1:-1:-1;;;;;1654:6:84;1637:23;1645:4;1637:23;1629:80;;;;-1:-1:-1;;;1629:80:84;;;;;;;:::i;:::-;1751:6;-1:-1:-1;;;;;1727:30:84;:20;:18;:20::i;:::-;-1:-1:-1;;;;;1727:30:84;;1719:87;;;;-1:-1:-1;;;1719:87:84;;;;;;;:::i;:::-;3772:36:::1;3790:17;3772;:36::i;:::-;3818:52;3840:17;3859:4;3865;3818:21;:52::i;2762:131::-:0;2840:7;2080:4;-1:-1:-1;;;;;2089:6:84;2072:23;;2064:92;;;;-1:-1:-1;;;2064:92:84;;5177:2:150;2064:92:84;;;5159:21:150;5216:2;5196:18;;;5189:30;5255:34;5235:18;;;5228:62;5326:26;5306:18;;;5299:54;5370:19;;2064:92:84;4975:420:150;2064:92:84;-1:-1:-1;;;;;;;;;;;;2762:131:84;:::o;2085:101:43:-;1355:13;:11;:13::i;:::-;2149:30:::1;2176:1;2149:18;:30::i;2031:212:42:-:0;1223:13;;965:10:65;;-1:-1:-1;;;;;1223:13:42;2130:24;;2122:78;;;;-1:-1:-1;;;2122:78:42;;5602:2:150;2122:78:42;;;5584:21:150;5641:2;5621:18;;;5614:30;5680:34;5660:18;;;5653:62;-1:-1:-1;;;5731:18:150;;;5724:39;5780:19;;2122:78:42;5400:405:150;2122:78:42;2210:26;2229:6;2210:18;:26::i;4625:241:12:-;2739:17;:15;:17::i;:::-;6359:8;:16;;-1:-1:-1;;6359:16:12;;;;;4696:18:::1;::::0;4703:10:::1;160:51:150::0;;4696:18:12::1;::::0;148:2:150;133:18;4696::12::1;;;;;;;4826:33;4842:10;4854:4;4826:15;:33::i;5162:90::-:0;5199:7;5225:20;:18;:20::i;:::-;5218:27;;5162:90;:::o;4132:296:4:-;-1:-1:-1;;;;;4312:14:4;;4271:4;4312:14;;;:4;:14;;;;;4360:25;4370:15;4360:7;:25;:::i;:::-;4343:13;;:42;;;;:78;;-1:-1:-1;4389:27:4;;;;;;:32;4343:78;4336:85;4132:296;-1:-1:-1;;;;4132:296:4:o;3497:228::-;2345:18:12;:16;:18::i;:::-;2373:24;650:1;2373:17;:24::i;:::-;3563:62:4::1;-1:-1:-1::0;;;;;3563:9:4::1;:26;3590:10;3610:4;3617:7:::0;3563:26:::1;:62::i;:::-;3636:32;3648:10;3660:7;3636:11;:32::i;:::-;3684:34;::::0;1201:25:150;;;3698:10:4::1;::::0;3684:34:::1;::::0;1189:2:150;1174:18;3684:34:4::1;;;;;;;2418:25:12::0;611:1;2418:17;:25::i;3168:128:4:-;3239:10;3925:17:12;3936:5;3925:10;:17::i;:::-;3261:28:4::1;3273:8;3283:5;3261:11;:28::i;:::-;3168:128:::0;;;:::o;4467:387::-;2345:18:12;:16;:18::i;:::-;2373:24;650:1;2373:17;:24::i;:::-;4554:10:4::1;4528:18;4549:16:::0;;;:4:::1;:16;::::0;;;;4583:13;;4575:46:::1;;;;-1:-1:-1::0;;;4575:46:4::1;;;;;;;;;;;;4639:27;::::0;::::1;::::0;::::1;;:32:::0;4631:71:::1;;;;-1:-1:-1::0;;;4631:71:4::1;;;;;;;;;;;;4713:27;::::0;::::1;:53:::0;;::::1;4750:15;4713:53:::0;;::::1;-1:-1:-1::0;;4713:53:4;;::::1;::::0;;;::::1;::::0;;;4801:10:::1;::::0;4781:66:::1;::::0;4813:33:::1;::::0;4831:15:::1;4813:33;::::0;::::1;:::i;:::-;4781:66;::::0;1201:25:150;;;1189:2;1174:18;4781:66:4::1;;;;;;;4518:336;2418:25:12::0;611:1;2418:17;:25::i;1436:178:42:-;1355:13:43;:11;:13::i;:::-;1525::42::1;:24:::0;;-1:-1:-1;;;;;1525:24:42;::::1;-1:-1:-1::0;;;;;;1525:24:42;;::::1;::::0;::::1;::::0;;;1589:7:::1;1534:6:43::0;;-1:-1:-1;;;;;1534:6:43;;1462:85;1589:7:42::1;-1:-1:-1::0;;;;;1564:43:42::1;;;;;;;;;;;1436:178:::0;:::o;5199:534:4:-;2345:18:12;:16;:18::i;:::-;2373:24;650:1;2373:17;:24::i;:::-;5305:10:4::1;5279:18;5300:16:::0;;;:4:::1;:16;::::0;;;;5344:27:::1;::::0;::::1;::::0;::::1;;:32:::0;;:115:::1;;-1:-1:-1::0;5414:27:4::1;::::0;::::1;::::0;:45:::1;::::0;5444:15:::1;::::0;5414:27:::1;;:45;:::i;:::-;5396:63;;:15;:63;5344:115;5327:354;;;5612:13:::0;;5639:7:::1;::::0;5612:23:::1;::::0;5628:7;;5612:23:::1;:::i;:::-;:34;;5604:66;;;;-1:-1:-1::0;;;5604:66:4::1;;;;;;;;;;;;5691:35;5701:10;5713:3;5718:7;5691:9;:35::i;:::-;5269:464;2418:25:12::0;611:1;2418:17;:25::i;3764:329:4:-;2345:18:12;:16;:18::i;:::-;2373:24;650:1;2373:17;:24::i;:::-;-1:-1:-1;;;;;3860:24:4;::::1;3852:53;;;;-1:-1:-1::0;;;3852:53:4::1;;;;;;;;;;;;3916:62;-1:-1:-1::0;;;;;3916:9:4::1;:26;3943:10;3963:4;3970:7:::0;3916:26:::1;:62::i;:::-;3989:32;4001:10;4013:7;3989:11;:32::i;:::-;4037:49;::::0;1201:25:150;;;-1:-1:-1;;;;;4037:49:4;::::1;::::0;4054:10:::1;::::0;4037:49:::1;::::0;1189:2:150;1174:18;4037:49:4::1;;;;;;;2418:25:12::0;611:1;2418:17;:25::i;6100:208::-;5374:13:48;;;;;;;5366:69;;;;-1:-1:-1;;;5366:69:48;;;;;;;:::i;:::-;6186:16:12::1;:14;:16::i;:::-;6212:62;-1:-1:-1::0;;;;;6231:20:12;::::1;::::0;:42:::1;;6267:6;6212:18;:62::i;6231:42::-;6254:10;6212:18;:62::i;:::-;-1:-1:-1::0;6284:8:12::1;:17:::0;;-1:-1:-1;;6284:17:12::1;;;::::0;;6100:208::o;7216:112::-;650:1;7275:18;6882:9;;;;;6786:112;7275:18;:27;;;7267:54;;;;-1:-1:-1;;;7267:54:12;;;;;;;;;;;6653:97;6723:9;:20;;-1:-1:-1;;6723:20:12;;;;;;;;;;;;6653:97::o;1175:140:80:-;-1:-1:-1;;;;;;;;;;;1254:54:80;-1:-1:-1;;;;;1254:54:80;;1175:140::o;6465:75:12:-;1355:13:43;:11;:13::i;2494:922:80:-;689:66;2910:48;;;2906:504;;;2974:37;2993:17;2974:18;:37::i;2906:504::-;3064:17;-1:-1:-1;;;;;3046:50:80;;:52;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;3046:52:80;;;;;;;;-1:-1:-1;;3046:52:80;;;;;;;;;;;;:::i;:::-;;;3042:291;;3262:56;;-1:-1:-1;;;3262:56:80;;7192:2:150;3262:56:80;;;7174:21:150;7231:2;7211:18;;;7204:30;7270:34;7250:18;;;7243:62;-1:-1:-1;;;7321:18:150;;;7314:44;7375:19;;3262:56:80;6990:410:150;3042:291:80;-1:-1:-1;;;;;;;;;;;3148:28:80;;3140:82;;;;-1:-1:-1;;;3140:82:80;;7607:2:150;3140:82:80;;;7589:21:150;7646:2;7626:18;;;7619:30;7685:34;7665:18;;;7658:62;-1:-1:-1;;;7736:18:150;;;7729:39;7785:19;;3140:82:80;7405:405:150;3140:82:80;3099:138;3346:53;3364:17;3383:4;3389:9;3346:17;:53::i;8056:110:12:-;8122:10;-1:-1:-1;;;;;8122:19:12;;;8114:45;;;;-1:-1:-1;;;8114:45:12;;;;;;;;;;;6144:411:4;-1:-1:-1;;;;;6310:14:4;;6253:20;6310:14;;;:4;:14;;;;;6339:13;;:22;-1:-1:-1;6335:214:4;;6392:13;;;6419:17;;6392:13;-1:-1:-1;6335:214:4;;;6517:13;;6482:5;;-1:-1:-1;6517:21:4;;6482:5;;6517:21;:::i;:::-;6501:37;;6144:411;;-1:-1:-1;;6144:411:4:o;7334:95:12:-;7389:8;5454;;;;;:17;:8;650:1;5454:17;;5384:94;7389:8;7381:41;;;;-1:-1:-1;;;7381:41:12;;;;;;;;;;;6546:70;1355:13:43;1620:130;1534:6;;-1:-1:-1;;;;;1534:6:43;965:10:65;1683:23:43;1675:68;;;;-1:-1:-1;;;1675:68:43;;8017:2:150;1675:68:43;;;7999:21:150;;;8036:18;;;8029:30;8095:34;8075:18;;;8068:62;8147:18;;1675:68:43;7815:356:150;1798:153:42;1887:13;1880:20;;-1:-1:-1;;;;;;1880:20:42;;;1910:34;1935:8;1910:24;:34::i;7435:99:12:-;7494:8;5454;;;;;:17;:8;650:1;5454:17;;5384:94;7494:8;7493:9;7485:42;;;;-1:-1:-1;;;7485:42:12;;;;;;;;;;;1355:203:91;1482:68;;-1:-1:-1;;;;;8396:32:150;;;1482:68:91;;;8378:51:150;8465:32;;8445:18;;;8438:60;8514:18;;;8507:34;;;1455:96:91;;1475:5;;-1:-1:-1;;;1505:27:91;8351:18:150;;1482:68:91;;;;-1:-1:-1;;1482:68:91;;;;;;;;;;;;;;-1:-1:-1;;;;;1482:68:91;-1:-1:-1;;;;;;1482:68:91;;;;;;;;;;1455:19;:96::i;:::-;1355:203;;;;:::o;6735:206:4:-;-1:-1:-1;;;;;6829:14:4;;6808:18;6829:14;;;:4;:14;;;;;6869:13;;:21;;6885:5;;6869:21;:::i;:::-;6853:37;;6905:29;;1201:25:150;;;-1:-1:-1;;;;;6905:29:4;;;;;1189:2:150;1174:18;6905:29:4;;;;;;;6798:143;6735:206;;:::o;7183:224::-;7266:15;7284:26;7295:5;7302:7;7284:10;:26::i;:::-;7266:44;-1:-1:-1;7320:36:4;-1:-1:-1;;;;;7320:9:4;:22;7343:3;7266:44;7320:22;:36::i;:::-;7385:5;-1:-1:-1;;;;;7371:29:4;;7392:7;7371:29;;;;1201:25:150;;1189:2;1174:18;;1055:177;7371:29:4;;;;;;;;7256:151;7183:224;;;:::o;747:59:65:-;5374:13:48;;;;;;;5366:69;;;;-1:-1:-1;;;5366:69:48;;;;;;;:::i;1406:259:80:-;-1:-1:-1;;;;;1713:19:64;;;1479:95:80;;;;-1:-1:-1;;;1479:95:80;;8754:2:150;1479:95:80;;;8736:21:150;8793:2;8773:18;;;8766:30;8832:34;8812:18;;;8805:62;-1:-1:-1;;;8883:18:150;;;8876:43;8936:19;;1479:95:80;8552:409:150;1479:95:80;-1:-1:-1;;;;;;;;;;;1584:74:80;;-1:-1:-1;;;;;;1584:74:80;-1:-1:-1;;;;;1584:74:80;;;;;;;;;;1406:259::o;2057:265::-;2165:29;2176:17;2165:10;:29::i;:::-;2222:1;2208:4;:11;:15;:28;;;;2227:9;2208:28;2204:112;;;2252:53;2281:17;2300:4;2252:28;:53::i;2687:187:43:-;2779:6;;;-1:-1:-1;;;;;2795:17:43;;;-1:-1:-1;;;;;;2795:17:43;;;;;;;2827:40;;2779:6;;;2795:17;2779:6;;2827:40;;2760:16;;2827:40;2750:124;2687:187;:::o;5196:642:91:-;5615:23;5641:69;5669:4;5641:69;;;;;;;;;;;;;;;;;5649:5;-1:-1:-1;;;;;5641:27:91;;;:69;;;;;:::i;:::-;5615:95;;5728:10;:17;5749:1;5728:22;:56;;;;5765:10;5754:30;;;;;;;;;;;;:::i;:::-;5720:111;;;;-1:-1:-1;;;5720:111:91;;9450:2:150;5720:111:91;;;9432:21:150;9489:2;9469:18;;;9462:30;9528:34;9508:18;;;9501:62;-1:-1:-1;;;9579:18:150;;;9572:40;9629:19;;5720:111:91;9248:406:150;941:175:91;1050:58;;-1:-1:-1;;;;;9851:32:150;;1050:58:91;;;9833:51:150;9900:18;;;9893:34;;;1023:86:91;;1043:5;;-1:-1:-1;;;1073:23:91;9806:18:150;;1050:58:91;9659:274:150;1771:152:80;1837:37;1856:17;1837:18;:37::i;:::-;1889:27;;-1:-1:-1;;;;;1889:27:80;;;;;;;;1771:152;:::o;6674:198:94:-;6757:12;6788:77;6809:6;6817:4;6788:77;;;;;;;;;;;;;;;;;:20;:77::i;:::-;6781:84;6674:198;-1:-1:-1;;;6674:198:94:o;4108:223::-;4241:12;4272:52;4294:6;4302:4;4308:1;4311:12;4272:21;:52::i;7058:325::-;7199:12;7224;7238:23;7265:6;-1:-1:-1;;;;;7265:19:94;7285:4;7265:25;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7223:67;;;;7307:69;7334:6;7342:7;7351:10;7363:12;7307:26;:69::i;:::-;7300:76;7058:325;-1:-1:-1;;;;;;7058:325:94:o;5165:446::-;5330:12;5387:5;5362:21;:30;;5354:81;;;;-1:-1:-1;;;5354:81:94;;10687:2:150;5354:81:94;;;10669:21:150;10726:2;10706:18;;;10699:30;10765:34;10745:18;;;10738:62;-1:-1:-1;;;10816:18:150;;;10809:36;10862:19;;5354:81:94;10485:402:150;5354:81:94;5446:12;5460:23;5487:6;-1:-1:-1;;;;;5487:11:94;5506:5;5513:4;5487:31;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5445:73;;;;5535:69;5562:6;5570:7;5579:10;5591:12;5535:26;:69::i;:::-;5528:76;5165:446;-1:-1:-1;;;;;;;5165:446:94:o;7671:628::-;7851:12;7879:7;7875:418;;;7906:10;:17;7927:1;7906:22;7902:286;;-1:-1:-1;;;;;1713:19:64;;;8113:60:94;;;;-1:-1:-1;;;8113:60:94;;11094:2:150;8113:60:94;;;11076:21:150;11133:2;11113:18;;;11106:30;11172:31;11152:18;;;11145:59;11221:18;;8113:60:94;10892:353:150;8113:60:94;-1:-1:-1;8208:10:94;8201:17;;7875:418;8249:33;8257:10;8269:12;8980:17;;:21;8976:379;;9208:10;9202:17;9264:15;9251:10;9247:2;9243:19;9236:44;8976:379;9331:12;9324:20;;-1:-1:-1;;;9324:20:94;;;;;;;;:::i;222:173:150:-;290:20;;-1:-1:-1;;;;;339:31:150;;329:42;;319:70;;385:1;382;375:12;319:70;222:173;;;:::o;400:186::-;459:6;512:2;500:9;491:7;487:23;483:32;480:52;;;528:1;525;518:12;480:52;551:29;570:9;551:29;:::i;1237:300::-;1305:6;1313;1366:2;1354:9;1345:7;1341:23;1337:32;1334:52;;;1382:1;1379;1372:12;1334:52;1405:29;1424:9;1405:29;:::i;:::-;1395:39;1503:2;1488:18;;;;1475:32;;-1:-1:-1;;;1237:300:150:o;1542:127::-;1603:10;1598:3;1594:20;1591:1;1584:31;1634:4;1631:1;1624:15;1658:4;1655:1;1648:15;1674:1018;1751:6;1759;1812:2;1800:9;1791:7;1787:23;1783:32;1780:52;;;1828:1;1825;1818:12;1780:52;1851:29;1870:9;1851:29;:::i;:::-;1841:39;;1931:2;1920:9;1916:18;1903:32;1958:18;1950:6;1947:30;1944:50;;;1990:1;1987;1980:12;1944:50;2013:22;;2066:4;2058:13;;2054:27;-1:-1:-1;2044:55:150;;2095:1;2092;2085:12;2044:55;2135:2;2122:16;2161:18;2153:6;2150:30;2147:56;;;2183:18;;:::i;:::-;2232:2;2226:9;2324:2;2286:17;;-1:-1:-1;;2282:31:150;;;2315:2;2278:40;2274:54;2262:67;;2359:18;2344:34;;2380:22;;;2341:62;2338:88;;;2406:18;;:::i;:::-;2442:2;2435:22;2466;;;2507:15;;;2524:2;2503:24;2500:37;-1:-1:-1;2497:57:150;;;2550:1;2547;2540:12;2497:57;2606:6;2601:2;2597;2593:11;2588:2;2580:6;2576:15;2563:50;2659:1;2654:2;2645:6;2637;2633:19;2629:28;2622:39;2680:6;2670:16;;;;;1674:1018;;;;;:::o;3080:226::-;3139:6;3192:2;3180:9;3171:7;3167:23;3163:32;3160:52;;;3208:1;3205;3198:12;3160:52;-1:-1:-1;3253:23:150;;3080:226;-1:-1:-1;3080:226:150:o;4149:408::-;4351:2;4333:21;;;4390:2;4370:18;;;4363:30;4429:34;4424:2;4409:18;;4402:62;-1:-1:-1;;;4495:2:150;4480:18;;4473:42;4547:3;4532:19;;4149:408::o;4562:::-;4764:2;4746:21;;;4803:2;4783:18;;;4776:30;4842:34;4837:2;4822:18;;4815:62;-1:-1:-1;;;4908:2:150;4893:18;;4886:42;4960:3;4945:19;;4562:408::o;5810:127::-;5871:10;5866:3;5862:20;5859:1;5852:31;5902:4;5899:1;5892:15;5926:4;5923:1;5916:15;5942:125;6007:9;;;6028:10;;;6025:36;;;6041:18;;:::i;6072:179::-;6171:14;6140:22;;;6164;;;6136:51;;6199:23;;6196:49;;;6225:18;;:::i;6256:128::-;6323:9;;;6344:11;;;6341:37;;;6358:18;;:::i;6389:407::-;6591:2;6573:21;;;6630:2;6610:18;;;6603:30;6669:34;6664:2;6649:18;;6642:62;-1:-1:-1;;;6735:2:150;6720:18;;6713:41;6786:3;6771:19;;6389:407::o;6801:184::-;6871:6;6924:2;6912:9;6903:7;6899:23;6895:32;6892:52;;;6940:1;6937;6930:12;6892:52;-1:-1:-1;6963:16:150;;6801:184;-1:-1:-1;6801:184:150:o;8966:277::-;9033:6;9086:2;9074:9;9065:7;9061:23;9057:32;9054:52;;;9102:1;9099;9092:12;9054:52;9134:9;9128:16;9187:5;9180:13;9173:21;9166:5;9163:32;9153:60;;9209:1;9206;9199:12;9938:250;10023:1;10033:113;10047:6;10044:1;10041:13;10033:113;;;10123:11;;;10117:18;10104:11;;;10097:39;10069:2;10062:10;10033:113;;;-1:-1:-1;;10180:1:150;10162:16;;10155:27;9938:250::o;10193:287::-;10322:3;10360:6;10354:13;10376:66;10435:6;10430:3;10423:4;10415:6;10411:17;10376:66;:::i;:::-;10458:16;;;;;10193:287;-1:-1:-1;;10193:287:150:o;11250:396::-;11399:2;11388:9;11381:21;11362:4;11431:6;11425:13;11474:6;11469:2;11458:9;11454:18;11447:34;11490:79;11562:6;11557:2;11546:9;11542:18;11537:2;11529:6;11525:15;11490:79;:::i;:::-;11630:2;11609:15;-1:-1:-1;;11605:29:150;11590:45;;;;11637:2;11586:54;;11250:396;-1:-1:-1;;11250:396:150:o","linkReferences":{},"immutableReferences":{"1526":[{"start":840,"length":32},{"start":2142,"length":32},{"start":3202,"length":32}],"1530":[{"start":1289,"length":32},{"start":3087,"length":32},{"start":3913,"length":32},{"start":5167,"length":32}],"1533":[{"start":1002,"length":32},{"start":2986,"length":32},{"start":3747,"length":32}],"1536":[{"start":1153,"length":32},{"start":3440,"length":32},{"start":3682,"length":32}],"20913":[{"start":1950,"length":32},{"start":2014,"length":32},{"start":2374,"length":32},{"start":2438,"length":32},{"start":2557,"length":32}],"4312":[{"start":460,"length":32}]}},"methodIdentifiers":{"acceptOwnership()":"79ba5097","authorized()":"456cb7c6","bond(address)":"247ce85b","bondToken()":"c28f4392","cancelWithdrawal()":"22611280","creditBond(address,uint256)":"be32d1f4","debitBond(address,uint256)":"391396de","deposit(uint256)":"b6b55f25","depositTo(address,uint256)":"ffaad6a5","getBondBalance(address)":"33613cbe","hasSufficientBond(address,uint256)":"a116e486","impl()":"8abf6077","inNonReentrant()":"3075db56","init(address)":"19ab453c","minBond()":"831518b7","owner()":"8da5cb5b","pause()":"8456cb59","paused()":"5c975abb","pendingOwner()":"e30c3978","proxiableUUID()":"52d1902d","renounceOwnership()":"715018a6","requestWithdrawal()":"dbaf2145","resolver()":"04f3bcec","transferOwnership(address)":"f2fde38b","unpause()":"3f4ba83a","upgradeTo(address)":"3659cfe6","upgradeToAndCall(address,bytes)":"4f1ef286","withdraw(address,uint256)":"f3fef3a3","withdrawalDelay()":"a7ab6961"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.30+commit.73712a01\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_authorized\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_bondToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_minBond\",\"type\":\"uint256\"},{\"internalType\":\"uint48\",\"name\":\"_withdrawalDelay\",\"type\":\"uint48\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"ACCESS_DENIED\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FUNC_NOT_IMPLEMENTED\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"INVALID_PAUSE_STATUS\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InsufficientBond\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidRecipient\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MustMaintainMinBond\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NoBondToWithdraw\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NoWithdrawalRequested\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"REENTRANT_CALL\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"WithdrawalAlreadyRequested\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZERO_ADDRESS\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZERO_VALUE\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"previousAdmin\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newAdmin\",\"type\":\"address\"}],\"name\":\"AdminChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"beacon\",\"type\":\"address\"}],\"name\":\"BeaconUpgraded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"BondCredited\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"BondDebited\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"BondDeposited\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"depositor\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"BondDepositedFor\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"BondWithdrawn\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"}],\"name\":\"Initialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferStarted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"Paused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"Unpaused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"Upgraded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"WithdrawalCancelled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"withdrawableAt\",\"type\":\"uint256\"}],\"name\":\"WithdrawalRequested\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"authorized\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"bond\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"},{\"internalType\":\"uint48\",\"name\":\"withdrawalRequestedAt\",\"type\":\"uint48\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"bondToken\",\"outputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"cancelWithdrawal\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_address\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_bond\",\"type\":\"uint256\"}],\"name\":\"creditBond\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_address\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_bond\",\"type\":\"uint256\"}],\"name\":\"debitBond\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amountDebited_\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"deposit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"depositTo\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_address\",\"type\":\"address\"}],\"name\":\"getBondBalance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_address\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_additionalBond\",\"type\":\"uint256\"}],\"name\":\"hasSufficientBond\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"impl\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"inNonReentrant\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"}],\"name\":\"init\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"minBond\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pause\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"paused\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pendingOwner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"proxiableUUID\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"requestWithdrawal\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"resolver\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"unpause\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"}],\"name\":\"upgradeTo\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"upgradeToAndCall\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"withdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"withdrawalDelay\",\"outputs\":[{\"internalType\":\"uint48\",\"name\":\"\",\"type\":\"uint48\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"custom:security-contact\":\"security@taiko.xyz\",\"events\":{\"AdminChanged(address,address)\":{\"details\":\"Emitted when the admin account has changed.\"},\"BeaconUpgraded(address)\":{\"details\":\"Emitted when the beacon is changed.\"},\"BondCredited(address,uint256)\":{\"params\":{\"account\":\"The account to which the bond was credited\",\"amount\":\"The amount credited\"}},\"BondDebited(address,uint256)\":{\"params\":{\"account\":\"The account from which the bond was debited\",\"amount\":\"The amount debited\"}},\"BondDeposited(address,uint256)\":{\"params\":{\"account\":\"The account that deposited the bond\",\"amount\":\"The amount deposited\"}},\"BondDepositedFor(address,address,uint256)\":{\"params\":{\"amount\":\"The amount deposited\",\"depositor\":\"The account that made the deposit\",\"recipient\":\"The account that received the bond credit\"}},\"BondWithdrawn(address,uint256)\":{\"params\":{\"account\":\"The account that withdrew the bond\",\"amount\":\"The amount withdrawn\"}},\"Initialized(uint8)\":{\"details\":\"Triggered when the contract has been initialized or reinitialized.\"},\"Paused(address)\":{\"params\":{\"account\":\"The account that paused the contract.\"}},\"Unpaused(address)\":{\"params\":{\"account\":\"The account that unpaused the contract.\"}},\"Upgraded(address)\":{\"details\":\"Emitted when the implementation is upgraded.\"}},\"kind\":\"dev\",\"methods\":{\"acceptOwnership()\":{\"details\":\"The new owner accepts the ownership transfer.\"},\"cancelWithdrawal()\":{\"details\":\"Can be called during or after the withdrawal delay period\"},\"constructor\":{\"params\":{\"_authorized\":\"The address of the authorized contract (Inbox)\",\"_bondToken\":\"The ERC20 bond token address\",\"_minBond\":\"The minimum bond required\",\"_withdrawalDelay\":\"The delay period for withdrawals (e.g., 7 days)\"}},\"creditBond(address,uint256)\":{\"params\":{\"_address\":\"The address to credit the bond to\",\"_bond\":\"The amount of bond to credit\"}},\"debitBond(address,uint256)\":{\"details\":\"Best effort means that if `_bond` is greater than the balance, the entire balance is debited instead\",\"params\":{\"_address\":\"The address to debit the bond from\",\"_bond\":\"The amount of bond to debit\"},\"returns\":{\"amountDebited_\":\"The actual amount debited\"}},\"deposit(uint256)\":{\"params\":{\"_amount\":\"The amount to deposit.\"}},\"depositTo(address,uint256)\":{\"params\":{\"_amount\":\"The amount to deposit.\",\"_recipient\":\"The address to credit the bond to.\"}},\"getBondBalance(address)\":{\"params\":{\"_address\":\"The address to get the bond balance for\"},\"returns\":{\"_0\":\"The bond balance of the address\"}},\"hasSufficientBond(address,uint256)\":{\"params\":{\"_additionalBond\":\"The additional bond required the account has to have on top of the minimum bond\",\"_address\":\"The address to check\"},\"returns\":{\"_0\":\"True if the account has sufficient bond and is active\"}},\"init(address)\":{\"params\":{\"_owner\":\"The owner of this contract\"}},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"paused()\":{\"returns\":{\"_0\":\"true if paused, false otherwise.\"}},\"pendingOwner()\":{\"details\":\"Returns the address of the pending owner.\"},\"proxiableUUID()\":{\"details\":\"Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the implementation. It is used to validate the implementation's compatibility when performing an upgrade. IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.\"},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner.\"},\"requestWithdrawal()\":{\"details\":\"Account cannot perform bond-restricted actions after requesting withdrawal\"},\"resolver()\":{\"returns\":{\"_0\":\"The address of this contract.\"}},\"transferOwnership(address)\":{\"details\":\"Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one. Can only be called by the current owner.\"},\"upgradeTo(address)\":{\"custom:oz-upgrades-unsafe-allow-reachable\":\"delegatecall\",\"details\":\"Upgrade the implementation of the proxy to `newImplementation`. Calls {_authorizeUpgrade}. Emits an {Upgraded} event.\"},\"upgradeToAndCall(address,bytes)\":{\"custom:oz-upgrades-unsafe-allow-reachable\":\"delegatecall\",\"details\":\"Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call encoded in `data`. Calls {_authorizeUpgrade}. Emits an {Upgraded} event.\"},\"withdraw(address,uint256)\":{\"details\":\"On L1, withdrawal is subject to time-based security. On L2, withdrawals are unrestricted.\",\"params\":{\"_amount\":\"The amount to withdraw.\",\"_to\":\"The recipient of withdrawn funds.\"}}},\"stateVariables\":{\"withdrawalDelay\":{\"details\":\"WARNING: In theory operations can remain unfinalized indefinitely, but in practice after the `extendedProvingWindow` the incentives are very strong for finalization. A safe value for this is `extendedProvingWindow` + buffer, for example, 2 weeks.\"}},\"title\":\"BondManager\",\"version\":1},\"userdoc\":{\"events\":{\"BondCredited(address,uint256)\":{\"notice\":\"Emitted when a bond is credited to an address\"},\"BondDebited(address,uint256)\":{\"notice\":\"Emitted when a bond is debited from an address\"},\"BondDeposited(address,uint256)\":{\"notice\":\"Emitted when a bond is deposited into the manager\"},\"BondDepositedFor(address,address,uint256)\":{\"notice\":\"Emitted when a bond is deposited for another address\"},\"BondWithdrawn(address,uint256)\":{\"notice\":\"Emitted when a bond is withdrawn from the manager\"},\"Paused(address)\":{\"notice\":\"Emitted when the contract is paused.\"},\"Unpaused(address)\":{\"notice\":\"Emitted when the contract is unpaused.\"},\"WithdrawalCancelled(address)\":{\"notice\":\"Emitted when a withdrawal request is cancelled\"},\"WithdrawalRequested(address,uint256)\":{\"notice\":\"Emitted when a withdrawal is requested\"}},\"kind\":\"user\",\"methods\":{\"authorized()\":{\"notice\":\"The address of the inbox contract that is allowed to call debitBond and creditBond\"},\"bond(address)\":{\"notice\":\"Per-account bond state\"},\"bondToken()\":{\"notice\":\"ERC20 token used as bond.\"},\"cancelWithdrawal()\":{\"notice\":\"Cancel withdrawal request to reactivate the account\"},\"constructor\":{\"notice\":\"Constructor disables initializers for upgradeable pattern\"},\"creditBond(address,uint256)\":{\"notice\":\"Credits a bond to an address\"},\"debitBond(address,uint256)\":{\"notice\":\"Debits a bond from an address with best effort\"},\"deposit(uint256)\":{\"notice\":\"Deposit ERC20 bond tokens into the manager.\"},\"depositTo(address,uint256)\":{\"notice\":\"Deposit ERC20 bond tokens for another address.\"},\"getBondBalance(address)\":{\"notice\":\"Gets the bond balance of an address\"},\"hasSufficientBond(address,uint256)\":{\"notice\":\"Checks if an account has sufficient bond and hasn't requested withdrawal\"},\"init(address)\":{\"notice\":\"Initializes the BondManager contract\"},\"minBond()\":{\"notice\":\"Minimum bond required\"},\"pause()\":{\"notice\":\"Pauses the contract.\"},\"paused()\":{\"notice\":\"Returns true if the contract is paused, and false otherwise.\"},\"requestWithdrawal()\":{\"notice\":\"Request to start the withdrawal process\"},\"resolver()\":{\"notice\":\"Returns the address of this contract.\"},\"unpause()\":{\"notice\":\"Unpauses the contract.\"},\"withdraw(address,uint256)\":{\"notice\":\"Withdraw bond to a recipient.\"},\"withdrawalDelay()\":{\"notice\":\"Time delay required before withdrawal after request\"}},\"notice\":\"L1 implementation of BondManager with time-based withdrawal mechanism\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/layer2/core/BondManager.sol\":\"BondManager\"},\"evmVersion\":\"shanghai\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@eth-fabric/urc/=node_modules/urc/src/\",\":@openzeppelin-upgrades/contracts/=node_modules/@openzeppelin/contracts-upgradeable/\",\":@openzeppelin/=node_modules/@openzeppelin/\",\":@optimism/=node_modules/optimism/\",\":@p256-verifier/contracts/=node_modules/p256-verifier/src/\",\":@risc0/contracts/=node_modules/risc0-ethereum/contracts/src/\",\":@solady/=node_modules/solady/\",\":@sp1-contracts/=node_modules/sp1-contracts/contracts/\",\":ds-test/=node_modules/ds-test/\",\":forge-std/=node_modules/forge-std/\",\":openzeppelin/=node_modules/@openzeppelin/\",\":optimism/=node_modules/optimism/\",\":p256-verifier/=node_modules/p256-verifier/\",\":risc0-ethereum/=node_modules/risc0-ethereum/\",\":script/=script/\",\":solady/src/=node_modules/solady/src/\",\":solady/utils/=node_modules/solady/src/utils/\",\":sp1-contracts/=node_modules/sp1-contracts/\",\":src/=contracts/\",\":test/=test/\",\":urc/=node_modules/urc/\"]},\"sources\":{\"contracts/layer2/core/BondManager.sol\":{\"keccak256\":\"0xde7c80e4018ab2e13d9877b4baca57de7324af0473856c31f26208515f013db6\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://a388f3bc303f04a1108ea6fb40e420a785ecfa3bcbbc574e03cdd8d8326bf352\",\"dweb:/ipfs/QmXYKGmHAivSxVWhzatXKZCxCd8oY2bjUfeGBFZ7iDRtw2\"]},\"contracts/layer2/core/IBondManager.sol\":{\"keccak256\":\"0x434f4528166c44d411603e3d221fdda8a2e5c73404e7b3256db8fea16fbe6954\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://d9ee4f789f154881b683cdfb92f0e63a8ddc4d32a7a9960020d6d253d72053d6\",\"dweb:/ipfs/QmViASeY24iP9QPosNAjSeHxSisjh1oLqMtqexAqpFjfsY\"]},\"contracts/shared/common/EssentialContract.sol\":{\"keccak256\":\"0x23f5e92e919911e82b308b25cf5c06050fd7c7799d44156d8492c2879a12c0a6\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://800f8bc7c8a362c8c684906e20c060d893cd4e8badf74cf12026e0cc159c846f\",\"dweb:/ipfs/QmPbre9Skvfz5K7TWVHYTGGnec99VMnY9EUFbB6VqMsd9C\"]},\"contracts/shared/common/IResolver.sol\":{\"keccak256\":\"0x01273635aa0be2ad8d6218ad520f824f2003eab82be1d14c46da58f2516a48dd\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://122dd3456077cf0be42bb974954c9918dca296415b076faeba0336ecee33de8e\",\"dweb:/ipfs/QmSr4WQEFDJwqyqpextmY8Fp3b7Z96SBHgAEKsvXe6dEZV\"]},\"node_modules/@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol\":{\"keccak256\":\"0x9140dabc466abab21b48b72dbda26736b1183a310d0e677d3719d201df026510\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://75267b14b60dc216d01d596a4008189a6c44d3314e53eded0edb1e757d95be16\",\"dweb:/ipfs/QmQoMaxTRT6V7uQj9USfdQH9jh1crywB9auVjThzUSAbG2\"]},\"node_modules/@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol\":{\"keccak256\":\"0x359a1ab89b46b9aba7bcad3fb651924baf4893d15153049b9976b0fc9be1358e\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://e89863421b4014b96a4b62be76eb3b9f0a8afe9684664a6f389124c0964bfe5c\",\"dweb:/ipfs/Qmbk7xr1irpDuU1WdxXgxELBXxs61rHhCgod7heVcvFx16\"]},\"node_modules/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\":{\"keccak256\":\"0x89be10e757d242e9b18d5a32c9fbe2019f6d63052bbe46397a430a1d60d7f794\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://f103ee2e4aecd37aac6ceefe670709cdd7613dee25fa2d4d9feaf7fc0aaa155e\",\"dweb:/ipfs/QmRiNZLoJk5k3HPMYGPGjZFd2ke1ZxjhJZkM45Ec9GH9hv\"]},\"node_modules/@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol\":{\"keccak256\":\"0x9c80f545915582e63fe206c6ce27cbe85a86fc10b9cd2a0e8c9488fb7c2ee422\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://310136ad60820af4177a11a61d77a3686faf5fca4942b600e08fc940db38396b\",\"dweb:/ipfs/QmbCzMNSTL7Zi7M4UCSqBrkHtp4jjxUnGbkneCZKdR1qeq\"]},\"node_modules/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol\":{\"keccak256\":\"0x75097e35253e7fb282ee4d7f27a80eaacfa759923185bf17302a89cbc059c5ef\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://8b06267c5f80bad727af3e48b1382333d591dad51376399ef2f6b0ee6d58bf95\",\"dweb:/ipfs/QmdU5La1agcQvghnfMpWZGDPz2TUDTCxUwTLKmuMRXBpAx\"]},\"node_modules/@openzeppelin/contracts/interfaces/IERC1967.sol\":{\"keccak256\":\"0x3cbef5ebc24b415252e2f8c0c9254555d30d9f085603b4b80d9b5ed20ab87e90\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://e8fa670c3bdce78e642cc6ae11c4cb38b133499cdce5e1990a9979d424703263\",\"dweb:/ipfs/QmVxeCUk4jL2pXQyhsoNJwyU874wRufS2WvGe8TgPKPqhE\"]},\"node_modules/@openzeppelin/contracts/interfaces/draft-IERC1822.sol\":{\"keccak256\":\"0x1d4afe6cb24200cc4545eed814ecf5847277dfe5d613a1707aad5fceecebcfff\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://383fb7b8181016ac5ccf07bc9cdb7c1b5045ea36e2cc4df52bcbf20396fc7688\",\"dweb:/ipfs/QmYJ7Cg4WmE3rR8KGQxjUCXFfTH6TcwZ2Z1f6tPrq7jHFr\"]},\"node_modules/@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol\":{\"keccak256\":\"0x3b21ae06bf5957f73fa16754b0669c77b7abd8ba6c072d35c3281d446fdb86c2\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://2db8e18505e86e02526847005d7287a33e397ed7fb9eaba3fd4a4a197add16e2\",\"dweb:/ipfs/QmW9BSuKTzHWHBNSHF4L8XfVuU1uJrP2vLg84YtBd8mL82\"]},\"node_modules/@openzeppelin/contracts/proxy/beacon/IBeacon.sol\":{\"keccak256\":\"0xd50a3421ac379ccb1be435fa646d66a65c986b4924f0849839f08692f39dde61\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://ada1e030c0231db8d143b44ce92b4d1158eedb087880cad6d8cc7bd7ebe7b354\",\"dweb:/ipfs/QmWZ2NHZweRpz1U9GF6R1h65ri76dnX7fNxLBeM2t5N5Ce\"]},\"node_modules/@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol\":{\"keccak256\":\"0xc6619957bcc6641fe8984bfaf9ff11a9e4b97d8149c0495f608f9a2416d7c5cf\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://543be67f7fa43b1b932637c1c7f12035f0f4b0f7ee2bd3c33841186f79c165c1\",\"dweb:/ipfs/QmSBPM2UVKbmJqWfD9i6hSiqbaE8TV4TSqfuiivziRRLKM\"]},\"node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"keccak256\":\"0x287b55befed2961a7eabd7d7b1b2839cbca8a5b80ef8dcbb25ed3d4c2002c305\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://bd39944e8fc06be6dbe2dd1d8449b5336e23c6a7ba3e8e9ae5ae0f37f35283f5\",\"dweb:/ipfs/QmPV3FGYjVwvKSgAXKUN3r9T9GwniZz83CxBpM7vyj2G53\"]},\"node_modules/@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol\":{\"keccak256\":\"0xb264c03a3442eb37a68ad620cefd1182766b58bee6cec40343480392d6b14d69\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://28879d01fd22c07b44f006612775f8577defbe459cb01685c5e25cd518c91a71\",\"dweb:/ipfs/QmVgfkwv2Fxw6hhTcDUZhE7NkoSKjab3ipM7UaRbt6uXb5\"]},\"node_modules/@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\":{\"keccak256\":\"0xabefac93435967b4d36a4fabcbdbb918d1f0b7ae3c3d85bc30923b326c927ed1\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://9d213d3befca47da33f6db0310826bcdb148299805c10d77175ecfe1d06a9a68\",\"dweb:/ipfs/QmRgCn6SP1hbBkExUADFuDo8xkT4UU47yjNF5FhCeRbQmS\"]},\"node_modules/@openzeppelin/contracts/utils/Address.sol\":{\"keccak256\":\"0x006dd67219697fe68d7fbfdea512e7c4cb64a43565ed86171d67e844982da6fa\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://2455248c8ddd9cc6a7af76a13973cddf222072427e7b0e2a7d1aff345145e931\",\"dweb:/ipfs/QmfYjnjRbWqYpuxurqveE6HtzsY1Xx323J428AKQgtBJZm\"]},\"node_modules/@openzeppelin/contracts/utils/StorageSlot.sol\":{\"keccak256\":\"0xf09e68aa0dc6722a25bc46490e8d48ed864466d17313b8a0b254c36b54e49899\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://e26daf81e2252dc1fe1ce0e4b55c2eb7c6d1ee84ae6558d1a9554432ea1d32da\",\"dweb:/ipfs/Qmb1UANWiWq5pCKbmHSu772hd4nt374dVaghGmwSVNuk8Q\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.30+commit.73712a01"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"address","name":"_authorized","type":"address"},{"internalType":"address","name":"_bondToken","type":"address"},{"internalType":"uint256","name":"_minBond","type":"uint256"},{"internalType":"uint48","name":"_withdrawalDelay","type":"uint48"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"type":"error","name":"ACCESS_DENIED"},{"inputs":[],"type":"error","name":"FUNC_NOT_IMPLEMENTED"},{"inputs":[],"type":"error","name":"INVALID_PAUSE_STATUS"},{"inputs":[],"type":"error","name":"InsufficientBond"},{"inputs":[],"type":"error","name":"InvalidRecipient"},{"inputs":[],"type":"error","name":"MustMaintainMinBond"},{"inputs":[],"type":"error","name":"NoBondToWithdraw"},{"inputs":[],"type":"error","name":"NoWithdrawalRequested"},{"inputs":[],"type":"error","name":"REENTRANT_CALL"},{"inputs":[],"type":"error","name":"WithdrawalAlreadyRequested"},{"inputs":[],"type":"error","name":"ZERO_ADDRESS"},{"inputs":[],"type":"error","name":"ZERO_VALUE"},{"inputs":[{"internalType":"address","name":"previousAdmin","type":"address","indexed":false},{"internalType":"address","name":"newAdmin","type":"address","indexed":false}],"type":"event","name":"AdminChanged","anonymous":false},{"inputs":[{"internalType":"address","name":"beacon","type":"address","indexed":true}],"type":"event","name":"BeaconUpgraded","anonymous":false},{"inputs":[{"internalType":"address","name":"account","type":"address","indexed":true},{"internalType":"uint256","name":"amount","type":"uint256","indexed":false}],"type":"event","name":"BondCredited","anonymous":false},{"inputs":[{"internalType":"address","name":"account","type":"address","indexed":true},{"internalType":"uint256","name":"amount","type":"uint256","indexed":false}],"type":"event","name":"BondDebited","anonymous":false},{"inputs":[{"internalType":"address","name":"account","type":"address","indexed":true},{"internalType":"uint256","name":"amount","type":"uint256","indexed":false}],"type":"event","name":"BondDeposited","anonymous":false},{"inputs":[{"internalType":"address","name":"depositor","type":"address","indexed":true},{"internalType":"address","name":"recipient","type":"address","indexed":true},{"internalType":"uint256","name":"amount","type":"uint256","indexed":false}],"type":"event","name":"BondDepositedFor","anonymous":false},{"inputs":[{"internalType":"address","name":"account","type":"address","indexed":true},{"internalType":"uint256","name":"amount","type":"uint256","indexed":false}],"type":"event","name":"BondWithdrawn","anonymous":false},{"inputs":[{"internalType":"uint8","name":"version","type":"uint8","indexed":false}],"type":"event","name":"Initialized","anonymous":false},{"inputs":[{"internalType":"address","name":"previousOwner","type":"address","indexed":true},{"internalType":"address","name":"newOwner","type":"address","indexed":true}],"type":"event","name":"OwnershipTransferStarted","anonymous":false},{"inputs":[{"internalType":"address","name":"previousOwner","type":"address","indexed":true},{"internalType":"address","name":"newOwner","type":"address","indexed":true}],"type":"event","name":"OwnershipTransferred","anonymous":false},{"inputs":[{"internalType":"address","name":"account","type":"address","indexed":false}],"type":"event","name":"Paused","anonymous":false},{"inputs":[{"internalType":"address","name":"account","type":"address","indexed":false}],"type":"event","name":"Unpaused","anonymous":false},{"inputs":[{"internalType":"address","name":"implementation","type":"address","indexed":true}],"type":"event","name":"Upgraded","anonymous":false},{"inputs":[{"internalType":"address","name":"account","type":"address","indexed":true}],"type":"event","name":"WithdrawalCancelled","anonymous":false},{"inputs":[{"internalType":"address","name":"account","type":"address","indexed":true},{"internalType":"uint256","name":"withdrawableAt","type":"uint256","indexed":false}],"type":"event","name":"WithdrawalRequested","anonymous":false},{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"acceptOwnership"},{"inputs":[],"stateMutability":"view","type":"function","name":"authorized","outputs":[{"internalType":"address","name":"","type":"address"}]},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"stateMutability":"view","type":"function","name":"bond","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint48","name":"withdrawalRequestedAt","type":"uint48"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"bondToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}]},{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"cancelWithdrawal"},{"inputs":[{"internalType":"address","name":"_address","type":"address"},{"internalType":"uint256","name":"_bond","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"creditBond"},{"inputs":[{"internalType":"address","name":"_address","type":"address"},{"internalType":"uint256","name":"_bond","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"debitBond","outputs":[{"internalType":"uint256","name":"amountDebited_","type":"uint256"}]},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"deposit"},{"inputs":[{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"depositTo"},{"inputs":[{"internalType":"address","name":"_address","type":"address"}],"stateMutability":"view","type":"function","name":"getBondBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"address","name":"_address","type":"address"},{"internalType":"uint256","name":"_additionalBond","type":"uint256"}],"stateMutability":"view","type":"function","name":"hasSufficientBond","outputs":[{"internalType":"bool","name":"","type":"bool"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"impl","outputs":[{"internalType":"address","name":"","type":"address"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"inNonReentrant","outputs":[{"internalType":"bool","name":"","type":"bool"}]},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"init"},{"inputs":[],"stateMutability":"view","type":"function","name":"minBond","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}]},{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"pause"},{"inputs":[],"stateMutability":"view","type":"function","name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"proxiableUUID","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}]},{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"renounceOwnership"},{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"requestWithdrawal"},{"inputs":[],"stateMutability":"view","type":"function","name":"resolver","outputs":[{"internalType":"address","name":"","type":"address"}]},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"transferOwnership"},{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"unpause"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"upgradeTo"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"stateMutability":"payable","type":"function","name":"upgradeToAndCall"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"withdraw"},{"inputs":[],"stateMutability":"view","type":"function","name":"withdrawalDelay","outputs":[{"internalType":"uint48","name":"","type":"uint48"}]}],"devdoc":{"kind":"dev","methods":{"acceptOwnership()":{"details":"The new owner accepts the ownership transfer."},"cancelWithdrawal()":{"details":"Can be called during or after the withdrawal delay period"},"constructor":{"params":{"_authorized":"The address of the authorized contract (Inbox)","_bondToken":"The ERC20 bond token address","_minBond":"The minimum bond required","_withdrawalDelay":"The delay period for withdrawals (e.g., 7 days)"}},"creditBond(address,uint256)":{"params":{"_address":"The address to credit the bond to","_bond":"The amount of bond to credit"}},"debitBond(address,uint256)":{"details":"Best effort means that if `_bond` is greater than the balance, the entire balance is debited instead","params":{"_address":"The address to debit the bond from","_bond":"The amount of bond to debit"},"returns":{"amountDebited_":"The actual amount debited"}},"deposit(uint256)":{"params":{"_amount":"The amount to deposit."}},"depositTo(address,uint256)":{"params":{"_amount":"The amount to deposit.","_recipient":"The address to credit the bond to."}},"getBondBalance(address)":{"params":{"_address":"The address to get the bond balance for"},"returns":{"_0":"The bond balance of the address"}},"hasSufficientBond(address,uint256)":{"params":{"_additionalBond":"The additional bond required the account has to have on top of the minimum bond","_address":"The address to check"},"returns":{"_0":"True if the account has sufficient bond and is active"}},"init(address)":{"params":{"_owner":"The owner of this contract"}},"owner()":{"details":"Returns the address of the current owner."},"paused()":{"returns":{"_0":"true if paused, false otherwise."}},"pendingOwner()":{"details":"Returns the address of the pending owner."},"proxiableUUID()":{"details":"Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the implementation. It is used to validate the implementation's compatibility when performing an upgrade. IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier."},"renounceOwnership()":{"details":"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner."},"requestWithdrawal()":{"details":"Account cannot perform bond-restricted actions after requesting withdrawal"},"resolver()":{"returns":{"_0":"The address of this contract."}},"transferOwnership(address)":{"details":"Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one. Can only be called by the current owner."},"upgradeTo(address)":{"custom:oz-upgrades-unsafe-allow-reachable":"delegatecall","details":"Upgrade the implementation of the proxy to `newImplementation`. Calls {_authorizeUpgrade}. Emits an {Upgraded} event."},"upgradeToAndCall(address,bytes)":{"custom:oz-upgrades-unsafe-allow-reachable":"delegatecall","details":"Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call encoded in `data`. Calls {_authorizeUpgrade}. Emits an {Upgraded} event."},"withdraw(address,uint256)":{"details":"On L1, withdrawal is subject to time-based security. On L2, withdrawals are unrestricted.","params":{"_amount":"The amount to withdraw.","_to":"The recipient of withdrawn funds."}}},"version":1},"userdoc":{"kind":"user","methods":{"authorized()":{"notice":"The address of the inbox contract that is allowed to call debitBond and creditBond"},"bond(address)":{"notice":"Per-account bond state"},"bondToken()":{"notice":"ERC20 token used as bond."},"cancelWithdrawal()":{"notice":"Cancel withdrawal request to reactivate the account"},"constructor":{"notice":"Constructor disables initializers for upgradeable pattern"},"creditBond(address,uint256)":{"notice":"Credits a bond to an address"},"debitBond(address,uint256)":{"notice":"Debits a bond from an address with best effort"},"deposit(uint256)":{"notice":"Deposit ERC20 bond tokens into the manager."},"depositTo(address,uint256)":{"notice":"Deposit ERC20 bond tokens for another address."},"getBondBalance(address)":{"notice":"Gets the bond balance of an address"},"hasSufficientBond(address,uint256)":{"notice":"Checks if an account has sufficient bond and hasn't requested withdrawal"},"init(address)":{"notice":"Initializes the BondManager contract"},"minBond()":{"notice":"Minimum bond required"},"pause()":{"notice":"Pauses the contract."},"paused()":{"notice":"Returns true if the contract is paused, and false otherwise."},"requestWithdrawal()":{"notice":"Request to start the withdrawal process"},"resolver()":{"notice":"Returns the address of this contract."},"unpause()":{"notice":"Unpauses the contract."},"withdraw(address,uint256)":{"notice":"Withdraw bond to a recipient."},"withdrawalDelay()":{"notice":"Time delay required before withdrawal after request"}},"version":1}},"settings":{"remappings":["@eth-fabric/urc/=node_modules/urc/src/","@openzeppelin-upgrades/contracts/=node_modules/@openzeppelin/contracts-upgradeable/","@openzeppelin/=node_modules/@openzeppelin/","@optimism/=node_modules/optimism/","@p256-verifier/contracts/=node_modules/p256-verifier/src/","@risc0/contracts/=node_modules/risc0-ethereum/contracts/src/","@solady/=node_modules/solady/","@sp1-contracts/=node_modules/sp1-contracts/contracts/","ds-test/=node_modules/ds-test/","forge-std/=node_modules/forge-std/","openzeppelin/=node_modules/@openzeppelin/","optimism/=node_modules/optimism/","p256-verifier/=node_modules/p256-verifier/","risc0-ethereum/=node_modules/risc0-ethereum/","script/=script/","solady/src/=node_modules/solady/src/","solady/utils/=node_modules/solady/src/utils/","sp1-contracts/=node_modules/sp1-contracts/","src/=contracts/","test/=test/","urc/=node_modules/urc/"],"optimizer":{"enabled":true,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"contracts/layer2/core/BondManager.sol":"BondManager"},"evmVersion":"shanghai","libraries":{}},"sources":{"contracts/layer2/core/BondManager.sol":{"keccak256":"0xde7c80e4018ab2e13d9877b4baca57de7324af0473856c31f26208515f013db6","urls":["bzz-raw://a388f3bc303f04a1108ea6fb40e420a785ecfa3bcbbc574e03cdd8d8326bf352","dweb:/ipfs/QmXYKGmHAivSxVWhzatXKZCxCd8oY2bjUfeGBFZ7iDRtw2"],"license":"MIT"},"contracts/layer2/core/IBondManager.sol":{"keccak256":"0x434f4528166c44d411603e3d221fdda8a2e5c73404e7b3256db8fea16fbe6954","urls":["bzz-raw://d9ee4f789f154881b683cdfb92f0e63a8ddc4d32a7a9960020d6d253d72053d6","dweb:/ipfs/QmViASeY24iP9QPosNAjSeHxSisjh1oLqMtqexAqpFjfsY"],"license":"MIT"},"contracts/shared/common/EssentialContract.sol":{"keccak256":"0x23f5e92e919911e82b308b25cf5c06050fd7c7799d44156d8492c2879a12c0a6","urls":["bzz-raw://800f8bc7c8a362c8c684906e20c060d893cd4e8badf74cf12026e0cc159c846f","dweb:/ipfs/QmPbre9Skvfz5K7TWVHYTGGnec99VMnY9EUFbB6VqMsd9C"],"license":"MIT"},"contracts/shared/common/IResolver.sol":{"keccak256":"0x01273635aa0be2ad8d6218ad520f824f2003eab82be1d14c46da58f2516a48dd","urls":["bzz-raw://122dd3456077cf0be42bb974954c9918dca296415b076faeba0336ecee33de8e","dweb:/ipfs/QmSr4WQEFDJwqyqpextmY8Fp3b7Z96SBHgAEKsvXe6dEZV"],"license":"MIT"},"node_modules/@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol":{"keccak256":"0x9140dabc466abab21b48b72dbda26736b1183a310d0e677d3719d201df026510","urls":["bzz-raw://75267b14b60dc216d01d596a4008189a6c44d3314e53eded0edb1e757d95be16","dweb:/ipfs/QmQoMaxTRT6V7uQj9USfdQH9jh1crywB9auVjThzUSAbG2"],"license":"MIT"},"node_modules/@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol":{"keccak256":"0x359a1ab89b46b9aba7bcad3fb651924baf4893d15153049b9976b0fc9be1358e","urls":["bzz-raw://e89863421b4014b96a4b62be76eb3b9f0a8afe9684664a6f389124c0964bfe5c","dweb:/ipfs/Qmbk7xr1irpDuU1WdxXgxELBXxs61rHhCgod7heVcvFx16"],"license":"MIT"},"node_modules/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol":{"keccak256":"0x89be10e757d242e9b18d5a32c9fbe2019f6d63052bbe46397a430a1d60d7f794","urls":["bzz-raw://f103ee2e4aecd37aac6ceefe670709cdd7613dee25fa2d4d9feaf7fc0aaa155e","dweb:/ipfs/QmRiNZLoJk5k3HPMYGPGjZFd2ke1ZxjhJZkM45Ec9GH9hv"],"license":"MIT"},"node_modules/@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol":{"keccak256":"0x9c80f545915582e63fe206c6ce27cbe85a86fc10b9cd2a0e8c9488fb7c2ee422","urls":["bzz-raw://310136ad60820af4177a11a61d77a3686faf5fca4942b600e08fc940db38396b","dweb:/ipfs/QmbCzMNSTL7Zi7M4UCSqBrkHtp4jjxUnGbkneCZKdR1qeq"],"license":"MIT"},"node_modules/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol":{"keccak256":"0x75097e35253e7fb282ee4d7f27a80eaacfa759923185bf17302a89cbc059c5ef","urls":["bzz-raw://8b06267c5f80bad727af3e48b1382333d591dad51376399ef2f6b0ee6d58bf95","dweb:/ipfs/QmdU5La1agcQvghnfMpWZGDPz2TUDTCxUwTLKmuMRXBpAx"],"license":"MIT"},"node_modules/@openzeppelin/contracts/interfaces/IERC1967.sol":{"keccak256":"0x3cbef5ebc24b415252e2f8c0c9254555d30d9f085603b4b80d9b5ed20ab87e90","urls":["bzz-raw://e8fa670c3bdce78e642cc6ae11c4cb38b133499cdce5e1990a9979d424703263","dweb:/ipfs/QmVxeCUk4jL2pXQyhsoNJwyU874wRufS2WvGe8TgPKPqhE"],"license":"MIT"},"node_modules/@openzeppelin/contracts/interfaces/draft-IERC1822.sol":{"keccak256":"0x1d4afe6cb24200cc4545eed814ecf5847277dfe5d613a1707aad5fceecebcfff","urls":["bzz-raw://383fb7b8181016ac5ccf07bc9cdb7c1b5045ea36e2cc4df52bcbf20396fc7688","dweb:/ipfs/QmYJ7Cg4WmE3rR8KGQxjUCXFfTH6TcwZ2Z1f6tPrq7jHFr"],"license":"MIT"},"node_modules/@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol":{"keccak256":"0x3b21ae06bf5957f73fa16754b0669c77b7abd8ba6c072d35c3281d446fdb86c2","urls":["bzz-raw://2db8e18505e86e02526847005d7287a33e397ed7fb9eaba3fd4a4a197add16e2","dweb:/ipfs/QmW9BSuKTzHWHBNSHF4L8XfVuU1uJrP2vLg84YtBd8mL82"],"license":"MIT"},"node_modules/@openzeppelin/contracts/proxy/beacon/IBeacon.sol":{"keccak256":"0xd50a3421ac379ccb1be435fa646d66a65c986b4924f0849839f08692f39dde61","urls":["bzz-raw://ada1e030c0231db8d143b44ce92b4d1158eedb087880cad6d8cc7bd7ebe7b354","dweb:/ipfs/QmWZ2NHZweRpz1U9GF6R1h65ri76dnX7fNxLBeM2t5N5Ce"],"license":"MIT"},"node_modules/@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol":{"keccak256":"0xc6619957bcc6641fe8984bfaf9ff11a9e4b97d8149c0495f608f9a2416d7c5cf","urls":["bzz-raw://543be67f7fa43b1b932637c1c7f12035f0f4b0f7ee2bd3c33841186f79c165c1","dweb:/ipfs/QmSBPM2UVKbmJqWfD9i6hSiqbaE8TV4TSqfuiivziRRLKM"],"license":"MIT"},"node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol":{"keccak256":"0x287b55befed2961a7eabd7d7b1b2839cbca8a5b80ef8dcbb25ed3d4c2002c305","urls":["bzz-raw://bd39944e8fc06be6dbe2dd1d8449b5336e23c6a7ba3e8e9ae5ae0f37f35283f5","dweb:/ipfs/QmPV3FGYjVwvKSgAXKUN3r9T9GwniZz83CxBpM7vyj2G53"],"license":"MIT"},"node_modules/@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol":{"keccak256":"0xb264c03a3442eb37a68ad620cefd1182766b58bee6cec40343480392d6b14d69","urls":["bzz-raw://28879d01fd22c07b44f006612775f8577defbe459cb01685c5e25cd518c91a71","dweb:/ipfs/QmVgfkwv2Fxw6hhTcDUZhE7NkoSKjab3ipM7UaRbt6uXb5"],"license":"MIT"},"node_modules/@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol":{"keccak256":"0xabefac93435967b4d36a4fabcbdbb918d1f0b7ae3c3d85bc30923b326c927ed1","urls":["bzz-raw://9d213d3befca47da33f6db0310826bcdb148299805c10d77175ecfe1d06a9a68","dweb:/ipfs/QmRgCn6SP1hbBkExUADFuDo8xkT4UU47yjNF5FhCeRbQmS"],"license":"MIT"},"node_modules/@openzeppelin/contracts/utils/Address.sol":{"keccak256":"0x006dd67219697fe68d7fbfdea512e7c4cb64a43565ed86171d67e844982da6fa","urls":["bzz-raw://2455248c8ddd9cc6a7af76a13973cddf222072427e7b0e2a7d1aff345145e931","dweb:/ipfs/QmfYjnjRbWqYpuxurqveE6HtzsY1Xx323J428AKQgtBJZm"],"license":"MIT"},"node_modules/@openzeppelin/contracts/utils/StorageSlot.sol":{"keccak256":"0xf09e68aa0dc6722a25bc46490e8d48ed864466d17313b8a0b254c36b54e49899","urls":["bzz-raw://e26daf81e2252dc1fe1ce0e4b55c2eb7c6d1ee84ae6558d1a9554432ea1d32da","dweb:/ipfs/Qmb1UANWiWq5pCKbmHSu772hd4nt374dVaghGmwSVNuk8Q"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[{"astId":11586,"contract":"contracts/layer2/core/BondManager.sol:BondManager","label":"_initialized","offset":0,"slot":"0","type":"t_uint8"},{"astId":11589,"contract":"contracts/layer2/core/BondManager.sol:BondManager","label":"_initializing","offset":1,"slot":"0","type":"t_bool"},{"astId":16420,"contract":"contracts/layer2/core/BondManager.sol:BondManager","label":"__gap","offset":0,"slot":"1","type":"t_array(t_uint256)50_storage"},{"astId":11331,"contract":"contracts/layer2/core/BondManager.sol:BondManager","label":"_owner","offset":0,"slot":"51","type":"t_address"},{"astId":11451,"contract":"contracts/layer2/core/BondManager.sol:BondManager","label":"__gap","offset":0,"slot":"52","type":"t_array(t_uint256)49_storage"},{"astId":11224,"contract":"contracts/layer2/core/BondManager.sol:BondManager","label":"_pendingOwner","offset":0,"slot":"101","type":"t_address"},{"astId":11318,"contract":"contracts/layer2/core/BondManager.sol:BondManager","label":"__gap","offset":0,"slot":"102","type":"t_array(t_uint256)49_storage"},{"astId":4316,"contract":"contracts/layer2/core/BondManager.sol:BondManager","label":"__gapFromOldAddressResolver","offset":0,"slot":"151","type":"t_array(t_uint256)50_storage"},{"astId":4319,"contract":"contracts/layer2/core/BondManager.sol:BondManager","label":"__reentry","offset":0,"slot":"201","type":"t_uint8"},{"astId":4321,"contract":"contracts/layer2/core/BondManager.sol:BondManager","label":"__paused","offset":1,"slot":"201","type":"t_uint8"},{"astId":4325,"contract":"contracts/layer2/core/BondManager.sol:BondManager","label":"__gap","offset":0,"slot":"202","type":"t_array(t_uint256)49_storage"},{"astId":1542,"contract":"contracts/layer2/core/BondManager.sol:BondManager","label":"bond","offset":0,"slot":"251","type":"t_mapping(t_address,t_struct(Bond)2043_storage)"},{"astId":1546,"contract":"contracts/layer2/core/BondManager.sol:BondManager","label":"__gap","offset":0,"slot":"252","type":"t_array(t_uint256)49_storage"}],"types":{"t_address":{"encoding":"inplace","label":"address","numberOfBytes":"20"},"t_array(t_uint256)49_storage":{"encoding":"inplace","label":"uint256[49]","numberOfBytes":"1568","base":"t_uint256"},"t_array(t_uint256)50_storage":{"encoding":"inplace","label":"uint256[50]","numberOfBytes":"1600","base":"t_uint256"},"t_bool":{"encoding":"inplace","label":"bool","numberOfBytes":"1"},"t_mapping(t_address,t_struct(Bond)2043_storage)":{"encoding":"mapping","key":"t_address","label":"mapping(address => struct IBondManager.Bond)","numberOfBytes":"32","value":"t_struct(Bond)2043_storage"},"t_struct(Bond)2043_storage":{"encoding":"inplace","label":"struct IBondManager.Bond","numberOfBytes":"64","members":[{"astId":2040,"contract":"contracts/layer2/core/BondManager.sol:BondManager","label":"balance","offset":0,"slot":"0","type":"t_uint256"},{"astId":2042,"contract":"contracts/layer2/core/BondManager.sol:BondManager","label":"withdrawalRequestedAt","offset":0,"slot":"1","type":"t_uint48"}]},"t_uint256":{"encoding":"inplace","label":"uint256","numberOfBytes":"32"},"t_uint48":{"encoding":"inplace","label":"uint48","numberOfBytes":"6"},"t_uint8":{"encoding":"inplace","label":"uint8","numberOfBytes":"1"}}},"ast":{"absolutePath":"contracts/layer2/core/BondManager.sol","id":2035,"exportedSymbols":{"BondManager":[2034],"EssentialContract":[4802],"IBondManager":[2159],"IERC20":[21865],"SafeERC20":[22302]},"nodeType":"SourceUnit","src":"32:8039:4","nodes":[{"id":1506,"nodeType":"PragmaDirective","src":"32:24:4","nodes":[],"literals":["solidity","^","0.8",".24"]},{"id":1508,"nodeType":"ImportDirective","src":"58:50:4","nodes":[],"absolutePath":"contracts/layer2/core/IBondManager.sol","file":"./IBondManager.sol","nameLocation":"-1:-1:-1","scope":2035,"sourceUnit":2160,"symbolAliases":[{"foreign":{"id":1507,"name":"IBondManager","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":2159,"src":"67:12:4","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":1510,"nodeType":"ImportDirective","src":"109:72:4","nodes":[],"absolutePath":"node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol","file":"@openzeppelin/contracts/token/ERC20/IERC20.sol","nameLocation":"-1:-1:-1","scope":2035,"sourceUnit":21866,"symbolAliases":[{"foreign":{"id":1509,"name":"IERC20","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":21865,"src":"118:6:4","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":1512,"nodeType":"ImportDirective","src":"182:84:4","nodes":[],"absolutePath":"node_modules/@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol","file":"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol","nameLocation":"-1:-1:-1","scope":2035,"sourceUnit":22303,"symbolAliases":[{"foreign":{"id":1511,"name":"SafeERC20","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":22302,"src":"191:9:4","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":1514,"nodeType":"ImportDirective","src":"267:76:4","nodes":[],"absolutePath":"contracts/shared/common/EssentialContract.sol","file":"src/shared/common/EssentialContract.sol","nameLocation":"-1:-1:-1","scope":2035,"sourceUnit":4803,"symbolAliases":[{"foreign":{"id":1513,"name":"EssentialContract","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":4802,"src":"276:17:4","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":2034,"nodeType":"ContractDefinition","src":"498:7572:4","nodes":[{"id":1523,"nodeType":"UsingForDirective","src":"560:27:4","nodes":[],"global":false,"libraryName":{"id":1520,"name":"SafeERC20","nameLocations":["566:9:4"],"nodeType":"IdentifierPath","referencedDeclaration":22302,"src":"566:9:4"},"typeName":{"id":1522,"nodeType":"UserDefinedTypeName","pathNode":{"id":1521,"name":"IERC20","nameLocations":["580:6:4"],"nodeType":"IdentifierPath","referencedDeclaration":21865,"src":"580:6:4"},"referencedDeclaration":21865,"src":"580:6:4","typeDescriptions":{"typeIdentifier":"t_contract$_IERC20_$21865","typeString":"contract IERC20"}}},{"id":1526,"nodeType":"VariableDeclaration","src":"858:35:4","nodes":[],"constant":false,"documentation":{"id":1524,"nodeType":"StructuredDocumentation","src":"759:94:4","text":"@notice The address of the inbox contract that is allowed to call debitBond and creditBond"},"functionSelector":"456cb7c6","mutability":"immutable","name":"authorized","nameLocation":"883:10:4","scope":2034,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":1525,"name":"address","nodeType":"ElementaryTypeName","src":"858:7:4","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"public"},{"id":1530,"nodeType":"VariableDeclaration","src":"942:33:4","nodes":[],"constant":false,"documentation":{"id":1527,"nodeType":"StructuredDocumentation","src":"900:37:4","text":"@notice ERC20 token used as bond."},"functionSelector":"c28f4392","mutability":"immutable","name":"bondToken","nameLocation":"966:9:4","scope":2034,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_IERC20_$21865","typeString":"contract IERC20"},"typeName":{"id":1529,"nodeType":"UserDefinedTypeName","pathNode":{"id":1528,"name":"IERC20","nameLocations":["942:6:4"],"nodeType":"IdentifierPath","referencedDeclaration":21865,"src":"942:6:4"},"referencedDeclaration":21865,"src":"942:6:4","typeDescriptions":{"typeIdentifier":"t_contract$_IERC20_$21865","typeString":"contract IERC20"}},"visibility":"public"},{"id":1533,"nodeType":"VariableDeclaration","src":"1020:32:4","nodes":[],"constant":false,"documentation":{"id":1531,"nodeType":"StructuredDocumentation","src":"982:33:4","text":"@notice Minimum bond required"},"functionSelector":"831518b7","mutability":"immutable","name":"minBond","nameLocation":"1045:7:4","scope":2034,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":1532,"name":"uint256","nodeType":"ElementaryTypeName","src":"1020:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"public"},{"id":1536,"nodeType":"VariableDeclaration","src":"1421:39:4","nodes":[],"constant":false,"documentation":{"id":1534,"nodeType":"StructuredDocumentation","src":"1059:357:4","text":"@notice Time delay required before withdrawal after request\n @dev WARNING: In theory operations can remain unfinalized indefinitely, but in practice\n after\n the `extendedProvingWindow` the incentives are very strong for finalization.\n A safe value for this is `extendedProvingWindow` + buffer, for example, 2 weeks."},"functionSelector":"a7ab6961","mutability":"immutable","name":"withdrawalDelay","nameLocation":"1445:15:4","scope":2034,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint48","typeString":"uint48"},"typeName":{"id":1535,"name":"uint48","nodeType":"ElementaryTypeName","src":"1421:6:4","typeDescriptions":{"typeIdentifier":"t_uint48","typeString":"uint48"}},"visibility":"public"},{"id":1542,"nodeType":"VariableDeclaration","src":"1506:49:4","nodes":[],"constant":false,"documentation":{"id":1537,"nodeType":"StructuredDocumentation","src":"1467:34:4","text":"@notice Per-account bond state"},"functionSelector":"247ce85b","mutability":"mutable","name":"bond","nameLocation":"1551:4:4","scope":2034,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_mapping$_t_address_$_t_struct$_Bond_$2043_storage_$","typeString":"mapping(address => struct IBondManager.Bond)"},"typeName":{"id":1541,"keyName":"account","keyNameLocation":"1522:7:4","keyType":{"id":1538,"name":"address","nodeType":"ElementaryTypeName","src":"1514:7:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"Mapping","src":"1506:37:4","typeDescriptions":{"typeIdentifier":"t_mapping$_t_address_$_t_struct$_Bond_$2043_storage_$","typeString":"mapping(address => struct IBondManager.Bond)"},"valueName":"bond","valueNameLocation":"1538:4:4","valueType":{"id":1540,"nodeType":"UserDefinedTypeName","pathNode":{"id":1539,"name":"Bond","nameLocations":["1533:4:4"],"nodeType":"IdentifierPath","referencedDeclaration":2043,"src":"1533:4:4"},"referencedDeclaration":2043,"src":"1533:4:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond"}}},"visibility":"public"},{"id":1546,"nodeType":"VariableDeclaration","src":"1562:25:4","nodes":[],"constant":false,"mutability":"mutable","name":"__gap","nameLocation":"1582:5:4","scope":2034,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_array$_t_uint256_$49_storage","typeString":"uint256[49]"},"typeName":{"baseType":{"id":1543,"name":"uint256","nodeType":"ElementaryTypeName","src":"1562:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":1545,"length":{"hexValue":"3439","id":1544,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"1570:2:4","typeDescriptions":{"typeIdentifier":"t_rational_49_by_1","typeString":"int_const 49"},"value":"49"},"nodeType":"ArrayTypeName","src":"1562:11:4","typeDescriptions":{"typeIdentifier":"t_array$_t_uint256_$49_storage_ptr","typeString":"uint256[49]"}},"visibility":"private"},{"id":1577,"nodeType":"FunctionDefinition","src":"2108:287:4","nodes":[],"body":{"id":1576,"nodeType":"Block","src":"2242:153:4","nodes":[],"statements":[{"expression":{"id":1560,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":1558,"name":"authorized","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1526,"src":"2252:10:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"id":1559,"name":"_authorized","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1549,"src":"2265:11:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"src":"2252:24:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":1561,"nodeType":"ExpressionStatement","src":"2252:24:4"},{"expression":{"id":1566,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":1562,"name":"bondToken","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1530,"src":"2286:9:4","typeDescriptions":{"typeIdentifier":"t_contract$_IERC20_$21865","typeString":"contract IERC20"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"arguments":[{"id":1564,"name":"_bondToken","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1551,"src":"2305:10:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":1563,"name":"IERC20","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":21865,"src":"2298:6:4","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_IERC20_$21865_$","typeString":"type(contract IERC20)"}},"id":1565,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"2298:18:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_IERC20_$21865","typeString":"contract IERC20"}},"src":"2286:30:4","typeDescriptions":{"typeIdentifier":"t_contract$_IERC20_$21865","typeString":"contract IERC20"}},"id":1567,"nodeType":"ExpressionStatement","src":"2286:30:4"},{"expression":{"id":1570,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":1568,"name":"minBond","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1533,"src":"2326:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"id":1569,"name":"_minBond","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1553,"src":"2336:8:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"2326:18:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":1571,"nodeType":"ExpressionStatement","src":"2326:18:4"},{"expression":{"id":1574,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":1572,"name":"withdrawalDelay","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1536,"src":"2354:15:4","typeDescriptions":{"typeIdentifier":"t_uint48","typeString":"uint48"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"id":1573,"name":"_withdrawalDelay","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1555,"src":"2372:16:4","typeDescriptions":{"typeIdentifier":"t_uint48","typeString":"uint48"}},"src":"2354:34:4","typeDescriptions":{"typeIdentifier":"t_uint48","typeString":"uint48"}},"id":1575,"nodeType":"ExpressionStatement","src":"2354:34:4"}]},"documentation":{"id":1547,"nodeType":"StructuredDocumentation","src":"1775:328:4","text":"@notice Constructor disables initializers for upgradeable pattern\n @param _authorized The address of the authorized contract (Inbox)\n @param _bondToken The ERC20 bond token address\n @param _minBond The minimum bond required\n @param _withdrawalDelay The delay period for withdrawals (e.g., 7 days)"},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":1556,"nodeType":"ParameterList","parameters":[{"constant":false,"id":1549,"mutability":"mutable","name":"_authorized","nameLocation":"2137:11:4","nodeType":"VariableDeclaration","scope":1577,"src":"2129:19:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":1548,"name":"address","nodeType":"ElementaryTypeName","src":"2129:7:4","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":1551,"mutability":"mutable","name":"_bondToken","nameLocation":"2166:10:4","nodeType":"VariableDeclaration","scope":1577,"src":"2158:18:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":1550,"name":"address","nodeType":"ElementaryTypeName","src":"2158:7:4","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":1553,"mutability":"mutable","name":"_minBond","nameLocation":"2194:8:4","nodeType":"VariableDeclaration","scope":1577,"src":"2186:16:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":1552,"name":"uint256","nodeType":"ElementaryTypeName","src":"2186:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"},{"constant":false,"id":1555,"mutability":"mutable","name":"_withdrawalDelay","nameLocation":"2219:16:4","nodeType":"VariableDeclaration","scope":1577,"src":"2212:23:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint48","typeString":"uint48"},"typeName":{"id":1554,"name":"uint48","nodeType":"ElementaryTypeName","src":"2212:6:4","typeDescriptions":{"typeIdentifier":"t_uint48","typeString":"uint48"}},"visibility":"internal"}],"src":"2119:122:4"},"returnParameters":{"id":1557,"nodeType":"ParameterList","parameters":[],"src":"2242:0:4"},"scope":2034,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":1590,"nodeType":"FunctionDefinition","src":"2503:92:4","nodes":[],"body":{"id":1589,"nodeType":"Block","src":"2554:41:4","nodes":[],"statements":[{"expression":{"arguments":[{"id":1586,"name":"_owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1580,"src":"2581:6:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":1585,"name":"__Essential_init","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":4585,"src":"2564:16:4","typeDescriptions":{"typeIdentifier":"t_function_internal_nonpayable$_t_address_$returns$__$","typeString":"function (address)"}},"id":1587,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"2564:24:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":1588,"nodeType":"ExpressionStatement","src":"2564:24:4"}]},"documentation":{"id":1578,"nodeType":"StructuredDocumentation","src":"2401:97:4","text":"@notice Initializes the BondManager contract\n @param _owner The owner of this contract"},"functionSelector":"19ab453c","implemented":true,"kind":"function","modifiers":[{"id":1583,"kind":"modifierInvocation","modifierName":{"id":1582,"name":"initializer","nameLocations":["2542:11:4"],"nodeType":"IdentifierPath","referencedDeclaration":11650,"src":"2542:11:4"},"nodeType":"ModifierInvocation","src":"2542:11:4"}],"name":"init","nameLocation":"2512:4:4","parameters":{"id":1581,"nodeType":"ParameterList","parameters":[{"constant":false,"id":1580,"mutability":"mutable","name":"_owner","nameLocation":"2525:6:4","nodeType":"VariableDeclaration","scope":1590,"src":"2517:14:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":1579,"name":"address","nodeType":"ElementaryTypeName","src":"2517:7:4","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"2516:16:4"},"returnParameters":{"id":1584,"nodeType":"ParameterList","parameters":[],"src":"2554:0:4"},"scope":2034,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":1621,"nodeType":"FunctionDefinition","src":"2803:326:4","nodes":[],"body":{"id":1620,"nodeType":"Block","src":"2968:161:4","nodes":[],"statements":[{"expression":{"id":1608,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":1603,"name":"amountDebited_","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1601,"src":"2978:14:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"arguments":[{"id":1605,"name":"_address","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1593,"src":"3006:8:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":1606,"name":"_bond","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1595,"src":"3016:5:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":1604,"name":"_debitBond","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1947,"src":"2995:10:4","typeDescriptions":{"typeIdentifier":"t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$_t_uint256_$","typeString":"function (address,uint256) returns (uint256)"}},"id":1607,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"2995:27:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"2978:44:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":1609,"nodeType":"ExpressionStatement","src":"2978:44:4"},{"condition":{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":1612,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"id":1610,"name":"amountDebited_","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1601,"src":"3036:14:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":">","rightExpression":{"hexValue":"30","id":1611,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"3053:1:4","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"},"src":"3036:18:4","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"id":1619,"nodeType":"IfStatement","src":"3032:91:4","trueBody":{"id":1618,"nodeType":"Block","src":"3056:67:4","statements":[{"eventCall":{"arguments":[{"id":1614,"name":"_address","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1593,"src":"3087:8:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":1615,"name":"amountDebited_","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1601,"src":"3097:14:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":1613,"name":"BondDebited","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":2050,"src":"3075:11:4","typeDescriptions":{"typeIdentifier":"t_function_event_nonpayable$_t_address_$_t_uint256_$returns$__$","typeString":"function (address,uint256)"}},"id":1616,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"3075:37:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":1617,"nodeType":"EmitStatement","src":"3070:42:4"}]}}]},"baseFunctions":[2102],"documentation":{"id":1591,"nodeType":"StructuredDocumentation","src":"2770:28:4","text":"@inheritdoc IBondManager"},"functionSelector":"391396de","implemented":true,"kind":"function","modifiers":[{"arguments":[{"id":1598,"name":"authorized","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1526,"src":"2911:10:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"id":1599,"kind":"modifierInvocation","modifierName":{"id":1597,"name":"onlyFrom","nameLocations":["2902:8:4"],"nodeType":"IdentifierPath","referencedDeclaration":4456,"src":"2902:8:4"},"nodeType":"ModifierInvocation","src":"2902:20:4"}],"name":"debitBond","nameLocation":"2812:9:4","parameters":{"id":1596,"nodeType":"ParameterList","parameters":[{"constant":false,"id":1593,"mutability":"mutable","name":"_address","nameLocation":"2839:8:4","nodeType":"VariableDeclaration","scope":1621,"src":"2831:16:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":1592,"name":"address","nodeType":"ElementaryTypeName","src":"2831:7:4","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":1595,"mutability":"mutable","name":"_bond","nameLocation":"2865:5:4","nodeType":"VariableDeclaration","scope":1621,"src":"2857:13:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":1594,"name":"uint256","nodeType":"ElementaryTypeName","src":"2857:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"2821:55:4"},"returnParameters":{"id":1602,"nodeType":"ParameterList","parameters":[{"constant":false,"id":1601,"mutability":"mutable","name":"amountDebited_","nameLocation":"2948:14:4","nodeType":"VariableDeclaration","scope":1621,"src":"2940:22:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":1600,"name":"uint256","nodeType":"ElementaryTypeName","src":"2940:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"2939:24:4"},"scope":2034,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":1638,"nodeType":"FunctionDefinition","src":"3168:128:4","nodes":[],"body":{"id":1637,"nodeType":"Block","src":"3251:45:4","nodes":[],"statements":[{"expression":{"arguments":[{"id":1633,"name":"_address","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1624,"src":"3273:8:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":1634,"name":"_bond","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1626,"src":"3283:5:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":1632,"name":"_creditBond","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1977,"src":"3261:11:4","typeDescriptions":{"typeIdentifier":"t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$__$","typeString":"function (address,uint256)"}},"id":1635,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"3261:28:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":1636,"nodeType":"ExpressionStatement","src":"3261:28:4"}]},"baseFunctions":[2110],"documentation":{"id":1622,"nodeType":"StructuredDocumentation","src":"3135:28:4","text":"@inheritdoc IBondManager"},"functionSelector":"be32d1f4","implemented":true,"kind":"function","modifiers":[{"arguments":[{"id":1629,"name":"authorized","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1526,"src":"3239:10:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"id":1630,"kind":"modifierInvocation","modifierName":{"id":1628,"name":"onlyFrom","nameLocations":["3230:8:4"],"nodeType":"IdentifierPath","referencedDeclaration":4456,"src":"3230:8:4"},"nodeType":"ModifierInvocation","src":"3230:20:4"}],"name":"creditBond","nameLocation":"3177:10:4","parameters":{"id":1627,"nodeType":"ParameterList","parameters":[{"constant":false,"id":1624,"mutability":"mutable","name":"_address","nameLocation":"3196:8:4","nodeType":"VariableDeclaration","scope":1638,"src":"3188:16:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":1623,"name":"address","nodeType":"ElementaryTypeName","src":"3188:7:4","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":1626,"mutability":"mutable","name":"_bond","nameLocation":"3214:5:4","nodeType":"VariableDeclaration","scope":1638,"src":"3206:13:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":1625,"name":"uint256","nodeType":"ElementaryTypeName","src":"3206:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"3187:33:4"},"returnParameters":{"id":1631,"nodeType":"ParameterList","parameters":[],"src":"3251:0:4"},"scope":2034,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":1651,"nodeType":"FunctionDefinition","src":"3335:123:4","nodes":[],"body":{"id":1650,"nodeType":"Block","src":"3409:49:4","nodes":[],"statements":[{"expression":{"arguments":[{"id":1647,"name":"_address","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1641,"src":"3442:8:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":1646,"name":"_getBondBalance","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":2021,"src":"3426:15:4","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_address_$returns$_t_uint256_$","typeString":"function (address) view returns (uint256)"}},"id":1648,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"3426:25:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"functionReturnParameters":1645,"id":1649,"nodeType":"Return","src":"3419:32:4"}]},"baseFunctions":[2118],"documentation":{"id":1639,"nodeType":"StructuredDocumentation","src":"3302:28:4","text":"@inheritdoc IBondManager"},"functionSelector":"33613cbe","implemented":true,"kind":"function","modifiers":[],"name":"getBondBalance","nameLocation":"3344:14:4","parameters":{"id":1642,"nodeType":"ParameterList","parameters":[{"constant":false,"id":1641,"mutability":"mutable","name":"_address","nameLocation":"3367:8:4","nodeType":"VariableDeclaration","scope":1651,"src":"3359:16:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":1640,"name":"address","nodeType":"ElementaryTypeName","src":"3359:7:4","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"3358:18:4"},"returnParameters":{"id":1645,"nodeType":"ParameterList","parameters":[{"constant":false,"id":1644,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":1651,"src":"3400:7:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":1643,"name":"uint256","nodeType":"ElementaryTypeName","src":"3400:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"3399:9:4"},"scope":2034,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":1684,"nodeType":"FunctionDefinition","src":"3497:228:4","nodes":[],"body":{"id":1683,"nodeType":"Block","src":"3553:172:4","nodes":[],"statements":[{"expression":{"arguments":[{"expression":{"id":1662,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3590:3:4","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":1663,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"3594:6:4","memberName":"sender","nodeType":"MemberAccess","src":"3590:10:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"arguments":[{"id":1666,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3610:4:4","typeDescriptions":{"typeIdentifier":"t_contract$_BondManager_$2034","typeString":"contract BondManager"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_BondManager_$2034","typeString":"contract BondManager"}],"id":1665,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3602:7:4","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":1664,"name":"address","nodeType":"ElementaryTypeName","src":"3602:7:4","typeDescriptions":{}}},"id":1667,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"3602:13:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":1668,"name":"_amount","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1654,"src":"3617:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":1659,"name":"bondToken","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1530,"src":"3563:9:4","typeDescriptions":{"typeIdentifier":"t_contract$_IERC20_$21865","typeString":"contract IERC20"}},"id":1661,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"3573:16:4","memberName":"safeTransferFrom","nodeType":"MemberAccess","referencedDeclaration":21986,"src":"3563:26:4","typeDescriptions":{"typeIdentifier":"t_function_internal_nonpayable$_t_contract$_IERC20_$21865_$_t_address_$_t_address_$_t_uint256_$returns$__$attached_to$_t_contract$_IERC20_$21865_$","typeString":"function (contract IERC20,address,address,uint256)"}},"id":1669,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"3563:62:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":1670,"nodeType":"ExpressionStatement","src":"3563:62:4"},{"expression":{"arguments":[{"expression":{"id":1672,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3648:3:4","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":1673,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"3652:6:4","memberName":"sender","nodeType":"MemberAccess","src":"3648:10:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":1674,"name":"_amount","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1654,"src":"3660:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":1671,"name":"_creditBond","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1977,"src":"3636:11:4","typeDescriptions":{"typeIdentifier":"t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$__$","typeString":"function (address,uint256)"}},"id":1675,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"3636:32:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":1676,"nodeType":"ExpressionStatement","src":"3636:32:4"},{"eventCall":{"arguments":[{"expression":{"id":1678,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3698:3:4","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":1679,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"3702:6:4","memberName":"sender","nodeType":"MemberAccess","src":"3698:10:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":1680,"name":"_amount","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1654,"src":"3710:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":1677,"name":"BondDeposited","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":2064,"src":"3684:13:4","typeDescriptions":{"typeIdentifier":"t_function_event_nonpayable$_t_address_$_t_uint256_$returns$__$","typeString":"function (address,uint256)"}},"id":1681,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"3684:34:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":1682,"nodeType":"EmitStatement","src":"3679:39:4"}]},"baseFunctions":[2124],"documentation":{"id":1652,"nodeType":"StructuredDocumentation","src":"3464:28:4","text":"@inheritdoc IBondManager"},"functionSelector":"b6b55f25","implemented":true,"kind":"function","modifiers":[{"id":1657,"kind":"modifierInvocation","modifierName":{"id":1656,"name":"nonReentrant","nameLocations":["3540:12:4"],"nodeType":"IdentifierPath","referencedDeclaration":4382,"src":"3540:12:4"},"nodeType":"ModifierInvocation","src":"3540:12:4"}],"name":"deposit","nameLocation":"3506:7:4","parameters":{"id":1655,"nodeType":"ParameterList","parameters":[{"constant":false,"id":1654,"mutability":"mutable","name":"_amount","nameLocation":"3522:7:4","nodeType":"VariableDeclaration","scope":1684,"src":"3514:15:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":1653,"name":"uint256","nodeType":"ElementaryTypeName","src":"3514:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"3513:17:4"},"returnParameters":{"id":1658,"nodeType":"ParameterList","parameters":[],"src":"3553:0:4"},"scope":2034,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":1730,"nodeType":"FunctionDefinition","src":"3764:329:4","nodes":[],"body":{"id":1729,"nodeType":"Block","src":"3842:251:4","nodes":[],"statements":[{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_address","typeString":"address"},"id":1700,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"id":1695,"name":"_recipient","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1687,"src":"3860:10:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"BinaryOperation","operator":"!=","rightExpression":{"arguments":[{"hexValue":"30","id":1698,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"3882:1:4","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"}],"id":1697,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3874:7:4","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":1696,"name":"address","nodeType":"ElementaryTypeName","src":"3874:7:4","typeDescriptions":{}}},"id":1699,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"3874:10:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"src":"3860:24:4","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"arguments":[],"expression":{"argumentTypes":[],"id":1701,"name":"InvalidRecipient","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":2025,"src":"3886:16:4","typeDescriptions":{"typeIdentifier":"t_function_error_pure$__$returns$_t_error_$","typeString":"function () pure returns (error)"}},"id":1702,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"3886:18:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_error","typeString":"error"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_error","typeString":"error"}],"id":1694,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18,-18],"referencedDeclaration":-18,"src":"3852:7:4","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_error_$returns$__$","typeString":"function (bool,error) pure"}},"id":1703,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"3852:53:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":1704,"nodeType":"ExpressionStatement","src":"3852:53:4"},{"expression":{"arguments":[{"expression":{"id":1708,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3943:3:4","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":1709,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"3947:6:4","memberName":"sender","nodeType":"MemberAccess","src":"3943:10:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"arguments":[{"id":1712,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3963:4:4","typeDescriptions":{"typeIdentifier":"t_contract$_BondManager_$2034","typeString":"contract BondManager"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_BondManager_$2034","typeString":"contract BondManager"}],"id":1711,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3955:7:4","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":1710,"name":"address","nodeType":"ElementaryTypeName","src":"3955:7:4","typeDescriptions":{}}},"id":1713,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"3955:13:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":1714,"name":"_amount","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1689,"src":"3970:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":1705,"name":"bondToken","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1530,"src":"3916:9:4","typeDescriptions":{"typeIdentifier":"t_contract$_IERC20_$21865","typeString":"contract IERC20"}},"id":1707,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"3926:16:4","memberName":"safeTransferFrom","nodeType":"MemberAccess","referencedDeclaration":21986,"src":"3916:26:4","typeDescriptions":{"typeIdentifier":"t_function_internal_nonpayable$_t_contract$_IERC20_$21865_$_t_address_$_t_address_$_t_uint256_$returns$__$attached_to$_t_contract$_IERC20_$21865_$","typeString":"function (contract IERC20,address,address,uint256)"}},"id":1715,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"3916:62:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":1716,"nodeType":"ExpressionStatement","src":"3916:62:4"},{"expression":{"arguments":[{"id":1718,"name":"_recipient","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1687,"src":"4001:10:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":1719,"name":"_amount","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1689,"src":"4013:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":1717,"name":"_creditBond","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1977,"src":"3989:11:4","typeDescriptions":{"typeIdentifier":"t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$__$","typeString":"function (address,uint256)"}},"id":1720,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"3989:32:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":1721,"nodeType":"ExpressionStatement","src":"3989:32:4"},{"eventCall":{"arguments":[{"expression":{"id":1723,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"4054:3:4","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":1724,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"4058:6:4","memberName":"sender","nodeType":"MemberAccess","src":"4054:10:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":1725,"name":"_recipient","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1687,"src":"4066:10:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":1726,"name":"_amount","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1689,"src":"4078:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":1722,"name":"BondDepositedFor","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":2073,"src":"4037:16:4","typeDescriptions":{"typeIdentifier":"t_function_event_nonpayable$_t_address_$_t_address_$_t_uint256_$returns$__$","typeString":"function (address,address,uint256)"}},"id":1727,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"4037:49:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":1728,"nodeType":"EmitStatement","src":"4032:54:4"}]},"baseFunctions":[2132],"documentation":{"id":1685,"nodeType":"StructuredDocumentation","src":"3731:28:4","text":"@inheritdoc IBondManager"},"functionSelector":"ffaad6a5","implemented":true,"kind":"function","modifiers":[{"id":1692,"kind":"modifierInvocation","modifierName":{"id":1691,"name":"nonReentrant","nameLocations":["3829:12:4"],"nodeType":"IdentifierPath","referencedDeclaration":4382,"src":"3829:12:4"},"nodeType":"ModifierInvocation","src":"3829:12:4"}],"name":"depositTo","nameLocation":"3773:9:4","parameters":{"id":1690,"nodeType":"ParameterList","parameters":[{"constant":false,"id":1687,"mutability":"mutable","name":"_recipient","nameLocation":"3791:10:4","nodeType":"VariableDeclaration","scope":1730,"src":"3783:18:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":1686,"name":"address","nodeType":"ElementaryTypeName","src":"3783:7:4","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":1689,"mutability":"mutable","name":"_amount","nameLocation":"3811:7:4","nodeType":"VariableDeclaration","scope":1730,"src":"3803:15:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":1688,"name":"uint256","nodeType":"ElementaryTypeName","src":"3803:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"3782:37:4"},"returnParameters":{"id":1693,"nodeType":"ParameterList","parameters":[],"src":"3842:0:4"},"scope":2034,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":1760,"nodeType":"FunctionDefinition","src":"4132:296:4","nodes":[],"body":{"id":1759,"nodeType":"Block","src":"4281:147:4","nodes":[],"statements":[{"assignments":[1742],"declarations":[{"constant":false,"id":1742,"mutability":"mutable","name":"bond_","nameLocation":"4304:5:4","nodeType":"VariableDeclaration","scope":1759,"src":"4291:18:4","stateVariable":false,"storageLocation":"storage","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond"},"typeName":{"id":1741,"nodeType":"UserDefinedTypeName","pathNode":{"id":1740,"name":"Bond","nameLocations":["4291:4:4"],"nodeType":"IdentifierPath","referencedDeclaration":2043,"src":"4291:4:4"},"referencedDeclaration":2043,"src":"4291:4:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond"}},"visibility":"internal"}],"id":1746,"initialValue":{"baseExpression":{"id":1743,"name":"bond","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1542,"src":"4312:4:4","typeDescriptions":{"typeIdentifier":"t_mapping$_t_address_$_t_struct$_Bond_$2043_storage_$","typeString":"mapping(address => struct IBondManager.Bond storage ref)"}},"id":1745,"indexExpression":{"id":1744,"name":"_address","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1733,"src":"4317:8:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"4312:14:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage","typeString":"struct IBondManager.Bond storage ref"}},"nodeType":"VariableDeclarationStatement","src":"4291:35:4"},{"expression":{"commonType":{"typeIdentifier":"t_bool","typeString":"bool"},"id":1757,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":1752,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":1747,"name":"bond_","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1742,"src":"4343:5:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond storage pointer"}},"id":1748,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"memberLocation":"4349:7:4","memberName":"balance","nodeType":"MemberAccess","referencedDeclaration":2040,"src":"4343:13:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":">=","rightExpression":{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":1751,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"id":1749,"name":"minBond","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1533,"src":"4360:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"+","rightExpression":{"id":1750,"name":"_additionalBond","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1735,"src":"4370:15:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"4360:25:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"4343:42:4","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"nodeType":"BinaryOperation","operator":"&&","rightExpression":{"commonType":{"typeIdentifier":"t_uint48","typeString":"uint48"},"id":1756,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":1753,"name":"bond_","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1742,"src":"4389:5:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond storage pointer"}},"id":1754,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"memberLocation":"4395:21:4","memberName":"withdrawalRequestedAt","nodeType":"MemberAccess","referencedDeclaration":2042,"src":"4389:27:4","typeDescriptions":{"typeIdentifier":"t_uint48","typeString":"uint48"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"30","id":1755,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4420:1:4","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"},"src":"4389:32:4","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"src":"4343:78:4","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"functionReturnParameters":1739,"id":1758,"nodeType":"Return","src":"4336:85:4"}]},"baseFunctions":[2150],"documentation":{"id":1731,"nodeType":"StructuredDocumentation","src":"4099:28:4","text":"@inheritdoc IBondManager"},"functionSelector":"a116e486","implemented":true,"kind":"function","modifiers":[],"name":"hasSufficientBond","nameLocation":"4141:17:4","parameters":{"id":1736,"nodeType":"ParameterList","parameters":[{"constant":false,"id":1733,"mutability":"mutable","name":"_address","nameLocation":"4176:8:4","nodeType":"VariableDeclaration","scope":1760,"src":"4168:16:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":1732,"name":"address","nodeType":"ElementaryTypeName","src":"4168:7:4","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":1735,"mutability":"mutable","name":"_additionalBond","nameLocation":"4202:15:4","nodeType":"VariableDeclaration","scope":1760,"src":"4194:23:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":1734,"name":"uint256","nodeType":"ElementaryTypeName","src":"4194:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"4158:65:4"},"returnParameters":{"id":1739,"nodeType":"ParameterList","parameters":[{"constant":false,"id":1738,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":1760,"src":"4271:4:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":1737,"name":"bool","nodeType":"ElementaryTypeName","src":"4271:4:4","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"4270:6:4"},"scope":2034,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":1812,"nodeType":"FunctionDefinition","src":"4467:387:4","nodes":[],"body":{"id":1811,"nodeType":"Block","src":"4518:336:4","nodes":[],"statements":[{"assignments":[1768],"declarations":[{"constant":false,"id":1768,"mutability":"mutable","name":"bond_","nameLocation":"4541:5:4","nodeType":"VariableDeclaration","scope":1811,"src":"4528:18:4","stateVariable":false,"storageLocation":"storage","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond"},"typeName":{"id":1767,"nodeType":"UserDefinedTypeName","pathNode":{"id":1766,"name":"Bond","nameLocations":["4528:4:4"],"nodeType":"IdentifierPath","referencedDeclaration":2043,"src":"4528:4:4"},"referencedDeclaration":2043,"src":"4528:4:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond"}},"visibility":"internal"}],"id":1773,"initialValue":{"baseExpression":{"id":1769,"name":"bond","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1542,"src":"4549:4:4","typeDescriptions":{"typeIdentifier":"t_mapping$_t_address_$_t_struct$_Bond_$2043_storage_$","typeString":"mapping(address => struct IBondManager.Bond storage ref)"}},"id":1772,"indexExpression":{"expression":{"id":1770,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"4554:3:4","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":1771,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"4558:6:4","memberName":"sender","nodeType":"MemberAccess","src":"4554:10:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"4549:16:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage","typeString":"struct IBondManager.Bond storage ref"}},"nodeType":"VariableDeclarationStatement","src":"4528:37:4"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":1778,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":1775,"name":"bond_","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1768,"src":"4583:5:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond storage pointer"}},"id":1776,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"memberLocation":"4589:7:4","memberName":"balance","nodeType":"MemberAccess","referencedDeclaration":2040,"src":"4583:13:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":">","rightExpression":{"hexValue":"30","id":1777,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4599:1:4","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"},"src":"4583:17:4","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"arguments":[],"expression":{"argumentTypes":[],"id":1779,"name":"NoBondToWithdraw","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":2029,"src":"4602:16:4","typeDescriptions":{"typeIdentifier":"t_function_error_pure$__$returns$_t_error_$","typeString":"function () pure returns (error)"}},"id":1780,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"4602:18:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_error","typeString":"error"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_error","typeString":"error"}],"id":1774,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18,-18],"referencedDeclaration":-18,"src":"4575:7:4","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_error_$returns$__$","typeString":"function (bool,error) pure"}},"id":1781,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"4575:46:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":1782,"nodeType":"ExpressionStatement","src":"4575:46:4"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint48","typeString":"uint48"},"id":1787,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":1784,"name":"bond_","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1768,"src":"4639:5:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond storage pointer"}},"id":1785,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"memberLocation":"4645:21:4","memberName":"withdrawalRequestedAt","nodeType":"MemberAccess","referencedDeclaration":2042,"src":"4639:27:4","typeDescriptions":{"typeIdentifier":"t_uint48","typeString":"uint48"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"30","id":1786,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4670:1:4","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"},"src":"4639:32:4","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"arguments":[],"expression":{"argumentTypes":[],"id":1788,"name":"WithdrawalAlreadyRequested","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":2033,"src":"4673:26:4","typeDescriptions":{"typeIdentifier":"t_function_error_pure$__$returns$_t_error_$","typeString":"function () pure returns (error)"}},"id":1789,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"4673:28:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_error","typeString":"error"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_error","typeString":"error"}],"id":1783,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18,-18],"referencedDeclaration":-18,"src":"4631:7:4","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_error_$returns$__$","typeString":"function (bool,error) pure"}},"id":1790,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"4631:71:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":1791,"nodeType":"ExpressionStatement","src":"4631:71:4"},{"expression":{"id":1800,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"expression":{"id":1792,"name":"bond_","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1768,"src":"4713:5:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond storage pointer"}},"id":1794,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":true,"memberLocation":"4719:21:4","memberName":"withdrawalRequestedAt","nodeType":"MemberAccess","referencedDeclaration":2042,"src":"4713:27:4","typeDescriptions":{"typeIdentifier":"t_uint48","typeString":"uint48"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"arguments":[{"expression":{"id":1797,"name":"block","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-4,"src":"4750:5:4","typeDescriptions":{"typeIdentifier":"t_magic_block","typeString":"block"}},"id":1798,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"4756:9:4","memberName":"timestamp","nodeType":"MemberAccess","src":"4750:15:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":1796,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4743:6:4","typeDescriptions":{"typeIdentifier":"t_type$_t_uint48_$","typeString":"type(uint48)"},"typeName":{"id":1795,"name":"uint48","nodeType":"ElementaryTypeName","src":"4743:6:4","typeDescriptions":{}}},"id":1799,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"4743:23:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint48","typeString":"uint48"}},"src":"4713:53:4","typeDescriptions":{"typeIdentifier":"t_uint48","typeString":"uint48"}},"id":1801,"nodeType":"ExpressionStatement","src":"4713:53:4"},{"eventCall":{"arguments":[{"expression":{"id":1803,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"4801:3:4","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":1804,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"4805:6:4","memberName":"sender","nodeType":"MemberAccess","src":"4801:10:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":1808,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":1805,"name":"block","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-4,"src":"4813:5:4","typeDescriptions":{"typeIdentifier":"t_magic_block","typeString":"block"}},"id":1806,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"4819:9:4","memberName":"timestamp","nodeType":"MemberAccess","src":"4813:15:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"+","rightExpression":{"id":1807,"name":"withdrawalDelay","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1536,"src":"4831:15:4","typeDescriptions":{"typeIdentifier":"t_uint48","typeString":"uint48"}},"src":"4813:33:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":1802,"name":"WithdrawalRequested","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":2087,"src":"4781:19:4","typeDescriptions":{"typeIdentifier":"t_function_event_nonpayable$_t_address_$_t_uint256_$returns$__$","typeString":"function (address,uint256)"}},"id":1809,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"4781:66:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":1810,"nodeType":"EmitStatement","src":"4776:71:4"}]},"baseFunctions":[2154],"documentation":{"id":1761,"nodeType":"StructuredDocumentation","src":"4434:28:4","text":"@inheritdoc IBondManager"},"functionSelector":"dbaf2145","implemented":true,"kind":"function","modifiers":[{"id":1764,"kind":"modifierInvocation","modifierName":{"id":1763,"name":"nonReentrant","nameLocations":["4505:12:4"],"nodeType":"IdentifierPath","referencedDeclaration":4382,"src":"4505:12:4"},"nodeType":"ModifierInvocation","src":"4505:12:4"}],"name":"requestWithdrawal","nameLocation":"4476:17:4","parameters":{"id":1762,"nodeType":"ParameterList","parameters":[],"src":"4493:2:4"},"returnParameters":{"id":1765,"nodeType":"ParameterList","parameters":[],"src":"4518:0:4"},"scope":2034,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":1847,"nodeType":"FunctionDefinition","src":"4893:267:4","nodes":[],"body":{"id":1846,"nodeType":"Block","src":"4943:217:4","nodes":[],"statements":[{"assignments":[1820],"declarations":[{"constant":false,"id":1820,"mutability":"mutable","name":"bond_","nameLocation":"4966:5:4","nodeType":"VariableDeclaration","scope":1846,"src":"4953:18:4","stateVariable":false,"storageLocation":"storage","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond"},"typeName":{"id":1819,"nodeType":"UserDefinedTypeName","pathNode":{"id":1818,"name":"Bond","nameLocations":["4953:4:4"],"nodeType":"IdentifierPath","referencedDeclaration":2043,"src":"4953:4:4"},"referencedDeclaration":2043,"src":"4953:4:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond"}},"visibility":"internal"}],"id":1825,"initialValue":{"baseExpression":{"id":1821,"name":"bond","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1542,"src":"4974:4:4","typeDescriptions":{"typeIdentifier":"t_mapping$_t_address_$_t_struct$_Bond_$2043_storage_$","typeString":"mapping(address => struct IBondManager.Bond storage ref)"}},"id":1824,"indexExpression":{"expression":{"id":1822,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"4979:3:4","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":1823,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"4983:6:4","memberName":"sender","nodeType":"MemberAccess","src":"4979:10:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"4974:16:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage","typeString":"struct IBondManager.Bond storage ref"}},"nodeType":"VariableDeclarationStatement","src":"4953:37:4"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint48","typeString":"uint48"},"id":1830,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":1827,"name":"bond_","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1820,"src":"5008:5:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond storage pointer"}},"id":1828,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"memberLocation":"5014:21:4","memberName":"withdrawalRequestedAt","nodeType":"MemberAccess","referencedDeclaration":2042,"src":"5008:27:4","typeDescriptions":{"typeIdentifier":"t_uint48","typeString":"uint48"}},"nodeType":"BinaryOperation","operator":">","rightExpression":{"hexValue":"30","id":1829,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5038:1:4","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"},"src":"5008:31:4","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"arguments":[],"expression":{"argumentTypes":[],"id":1831,"name":"NoWithdrawalRequested","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":2031,"src":"5041:21:4","typeDescriptions":{"typeIdentifier":"t_function_error_pure$__$returns$_t_error_$","typeString":"function () pure returns (error)"}},"id":1832,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"5041:23:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_error","typeString":"error"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_error","typeString":"error"}],"id":1826,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18,-18],"referencedDeclaration":-18,"src":"5000:7:4","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_error_$returns$__$","typeString":"function (bool,error) pure"}},"id":1833,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"5000:65:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":1834,"nodeType":"ExpressionStatement","src":"5000:65:4"},{"expression":{"id":1839,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"expression":{"id":1835,"name":"bond_","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1820,"src":"5076:5:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond storage pointer"}},"id":1837,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":true,"memberLocation":"5082:21:4","memberName":"withdrawalRequestedAt","nodeType":"MemberAccess","referencedDeclaration":2042,"src":"5076:27:4","typeDescriptions":{"typeIdentifier":"t_uint48","typeString":"uint48"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"hexValue":"30","id":1838,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5106:1:4","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"},"src":"5076:31:4","typeDescriptions":{"typeIdentifier":"t_uint48","typeString":"uint48"}},"id":1840,"nodeType":"ExpressionStatement","src":"5076:31:4"},{"eventCall":{"arguments":[{"expression":{"id":1842,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"5142:3:4","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":1843,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"5146:6:4","memberName":"sender","nodeType":"MemberAccess","src":"5142:10:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":1841,"name":"WithdrawalCancelled","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":2092,"src":"5122:19:4","typeDescriptions":{"typeIdentifier":"t_function_event_nonpayable$_t_address_$returns$__$","typeString":"function (address)"}},"id":1844,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"5122:31:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":1845,"nodeType":"EmitStatement","src":"5117:36:4"}]},"baseFunctions":[2158],"documentation":{"id":1813,"nodeType":"StructuredDocumentation","src":"4860:28:4","text":"@inheritdoc IBondManager"},"functionSelector":"22611280","implemented":true,"kind":"function","modifiers":[{"id":1816,"kind":"modifierInvocation","modifierName":{"id":1815,"name":"nonReentrant","nameLocations":["4930:12:4"],"nodeType":"IdentifierPath","referencedDeclaration":4382,"src":"4930:12:4"},"nodeType":"ModifierInvocation","src":"4930:12:4"}],"name":"cancelWithdrawal","nameLocation":"4902:16:4","parameters":{"id":1814,"nodeType":"ParameterList","parameters":[],"src":"4918:2:4"},"returnParameters":{"id":1817,"nodeType":"ParameterList","parameters":[],"src":"4943:0:4"},"scope":2034,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":1898,"nodeType":"FunctionDefinition","src":"5199:534:4","nodes":[],"body":{"id":1897,"nodeType":"Block","src":"5269:464:4","nodes":[],"statements":[{"assignments":[1859],"declarations":[{"constant":false,"id":1859,"mutability":"mutable","name":"bond_","nameLocation":"5292:5:4","nodeType":"VariableDeclaration","scope":1897,"src":"5279:18:4","stateVariable":false,"storageLocation":"storage","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond"},"typeName":{"id":1858,"nodeType":"UserDefinedTypeName","pathNode":{"id":1857,"name":"Bond","nameLocations":["5279:4:4"],"nodeType":"IdentifierPath","referencedDeclaration":2043,"src":"5279:4:4"},"referencedDeclaration":2043,"src":"5279:4:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond"}},"visibility":"internal"}],"id":1864,"initialValue":{"baseExpression":{"id":1860,"name":"bond","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1542,"src":"5300:4:4","typeDescriptions":{"typeIdentifier":"t_mapping$_t_address_$_t_struct$_Bond_$2043_storage_$","typeString":"mapping(address => struct IBondManager.Bond storage ref)"}},"id":1863,"indexExpression":{"expression":{"id":1861,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"5305:3:4","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":1862,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"5309:6:4","memberName":"sender","nodeType":"MemberAccess","src":"5305:10:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"5300:16:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage","typeString":"struct IBondManager.Bond storage ref"}},"nodeType":"VariableDeclarationStatement","src":"5279:37:4"},{"condition":{"commonType":{"typeIdentifier":"t_bool","typeString":"bool"},"id":1876,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"commonType":{"typeIdentifier":"t_uint48","typeString":"uint48"},"id":1868,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":1865,"name":"bond_","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1859,"src":"5344:5:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond storage pointer"}},"id":1866,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"memberLocation":"5350:21:4","memberName":"withdrawalRequestedAt","nodeType":"MemberAccess","referencedDeclaration":2042,"src":"5344:27:4","typeDescriptions":{"typeIdentifier":"t_uint48","typeString":"uint48"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"30","id":1867,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5375:1:4","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"},"src":"5344:32:4","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"nodeType":"BinaryOperation","operator":"||","rightExpression":{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":1875,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":1869,"name":"block","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-4,"src":"5396:5:4","typeDescriptions":{"typeIdentifier":"t_magic_block","typeString":"block"}},"id":1870,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"5402:9:4","memberName":"timestamp","nodeType":"MemberAccess","src":"5396:15:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"<","rightExpression":{"commonType":{"typeIdentifier":"t_uint48","typeString":"uint48"},"id":1874,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":1871,"name":"bond_","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1859,"src":"5414:5:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond storage pointer"}},"id":1872,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"memberLocation":"5420:21:4","memberName":"withdrawalRequestedAt","nodeType":"MemberAccess","referencedDeclaration":2042,"src":"5414:27:4","typeDescriptions":{"typeIdentifier":"t_uint48","typeString":"uint48"}},"nodeType":"BinaryOperation","operator":"+","rightExpression":{"id":1873,"name":"withdrawalDelay","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1536,"src":"5444:15:4","typeDescriptions":{"typeIdentifier":"t_uint48","typeString":"uint48"}},"src":"5414:45:4","typeDescriptions":{"typeIdentifier":"t_uint48","typeString":"uint48"}},"src":"5396:63:4","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"src":"5344:115:4","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"id":1889,"nodeType":"IfStatement","src":"5327:354:4","trueBody":{"id":1888,"nodeType":"Block","src":"5470:211:4","statements":[{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":1883,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":1881,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":1878,"name":"bond_","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1859,"src":"5612:5:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond storage pointer"}},"id":1879,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"memberLocation":"5618:7:4","memberName":"balance","nodeType":"MemberAccess","referencedDeclaration":2040,"src":"5612:13:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"-","rightExpression":{"id":1880,"name":"_amount","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1852,"src":"5628:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"5612:23:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":">=","rightExpression":{"id":1882,"name":"minBond","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1533,"src":"5639:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"5612:34:4","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"arguments":[],"expression":{"argumentTypes":[],"id":1884,"name":"MustMaintainMinBond","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":2027,"src":"5648:19:4","typeDescriptions":{"typeIdentifier":"t_function_error_pure$__$returns$_t_error_$","typeString":"function () pure returns (error)"}},"id":1885,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"5648:21:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_error","typeString":"error"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_error","typeString":"error"}],"id":1877,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18,-18],"referencedDeclaration":-18,"src":"5604:7:4","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_error_$returns$__$","typeString":"function (bool,error) pure"}},"id":1886,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"5604:66:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":1887,"nodeType":"ExpressionStatement","src":"5604:66:4"}]}},{"expression":{"arguments":[{"expression":{"id":1891,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"5701:3:4","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":1892,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"5705:6:4","memberName":"sender","nodeType":"MemberAccess","src":"5701:10:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":1893,"name":"_to","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1850,"src":"5713:3:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":1894,"name":"_amount","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1852,"src":"5718:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":1890,"name":"_withdraw","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":2007,"src":"5691:9:4","typeDescriptions":{"typeIdentifier":"t_function_internal_nonpayable$_t_address_$_t_address_$_t_uint256_$returns$__$","typeString":"function (address,address,uint256)"}},"id":1895,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"5691:35:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":1896,"nodeType":"ExpressionStatement","src":"5691:35:4"}]},"baseFunctions":[2140],"documentation":{"id":1848,"nodeType":"StructuredDocumentation","src":"5166:28:4","text":"@inheritdoc IBondManager"},"functionSelector":"f3fef3a3","implemented":true,"kind":"function","modifiers":[{"id":1855,"kind":"modifierInvocation","modifierName":{"id":1854,"name":"nonReentrant","nameLocations":["5256:12:4"],"nodeType":"IdentifierPath","referencedDeclaration":4382,"src":"5256:12:4"},"nodeType":"ModifierInvocation","src":"5256:12:4"}],"name":"withdraw","nameLocation":"5208:8:4","parameters":{"id":1853,"nodeType":"ParameterList","parameters":[{"constant":false,"id":1850,"mutability":"mutable","name":"_to","nameLocation":"5225:3:4","nodeType":"VariableDeclaration","scope":1898,"src":"5217:11:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":1849,"name":"address","nodeType":"ElementaryTypeName","src":"5217:7:4","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":1852,"mutability":"mutable","name":"_amount","nameLocation":"5238:7:4","nodeType":"VariableDeclaration","scope":1898,"src":"5230:15:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":1851,"name":"uint256","nodeType":"ElementaryTypeName","src":"5230:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"5216:30:4"},"returnParameters":{"id":1856,"nodeType":"ParameterList","parameters":[],"src":"5269:0:4"},"scope":2034,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":1947,"nodeType":"FunctionDefinition","src":"6144:411:4","nodes":[],"body":{"id":1946,"nodeType":"Block","src":"6279:276:4","nodes":[],"statements":[{"assignments":[1910],"declarations":[{"constant":false,"id":1910,"mutability":"mutable","name":"bond_","nameLocation":"6302:5:4","nodeType":"VariableDeclaration","scope":1946,"src":"6289:18:4","stateVariable":false,"storageLocation":"storage","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond"},"typeName":{"id":1909,"nodeType":"UserDefinedTypeName","pathNode":{"id":1908,"name":"Bond","nameLocations":["6289:4:4"],"nodeType":"IdentifierPath","referencedDeclaration":2043,"src":"6289:4:4"},"referencedDeclaration":2043,"src":"6289:4:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond"}},"visibility":"internal"}],"id":1914,"initialValue":{"baseExpression":{"id":1911,"name":"bond","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1542,"src":"6310:4:4","typeDescriptions":{"typeIdentifier":"t_mapping$_t_address_$_t_struct$_Bond_$2043_storage_$","typeString":"mapping(address => struct IBondManager.Bond storage ref)"}},"id":1913,"indexExpression":{"id":1912,"name":"_address","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1901,"src":"6315:8:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"6310:14:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage","typeString":"struct IBondManager.Bond storage ref"}},"nodeType":"VariableDeclarationStatement","src":"6289:35:4"},{"condition":{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":1918,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":1915,"name":"bond_","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1910,"src":"6339:5:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond storage pointer"}},"id":1916,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"memberLocation":"6345:7:4","memberName":"balance","nodeType":"MemberAccess","referencedDeclaration":2040,"src":"6339:13:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"<=","rightExpression":{"id":1917,"name":"_bond","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1903,"src":"6356:5:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"6339:22:4","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"falseBody":{"id":1944,"nodeType":"Block","src":"6453:96:4","statements":[{"expression":{"id":1933,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":1931,"name":"bondDebited_","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1906,"src":"6467:12:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"id":1932,"name":"_bond","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1903,"src":"6482:5:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"6467:20:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":1934,"nodeType":"ExpressionStatement","src":"6467:20:4"},{"expression":{"id":1942,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"expression":{"id":1935,"name":"bond_","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1910,"src":"6501:5:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond storage pointer"}},"id":1937,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":true,"memberLocation":"6507:7:4","memberName":"balance","nodeType":"MemberAccess","referencedDeclaration":2040,"src":"6501:13:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":1941,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":1938,"name":"bond_","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1910,"src":"6517:5:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond storage pointer"}},"id":1939,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"memberLocation":"6523:7:4","memberName":"balance","nodeType":"MemberAccess","referencedDeclaration":2040,"src":"6517:13:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"-","rightExpression":{"id":1940,"name":"_bond","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1903,"src":"6533:5:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"6517:21:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"6501:37:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":1943,"nodeType":"ExpressionStatement","src":"6501:37:4"}]},"id":1945,"nodeType":"IfStatement","src":"6335:214:4","trueBody":{"id":1930,"nodeType":"Block","src":"6363:84:4","statements":[{"expression":{"id":1922,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":1919,"name":"bondDebited_","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1906,"src":"6377:12:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"expression":{"id":1920,"name":"bond_","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1910,"src":"6392:5:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond storage pointer"}},"id":1921,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"memberLocation":"6398:7:4","memberName":"balance","nodeType":"MemberAccess","referencedDeclaration":2040,"src":"6392:13:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"6377:28:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":1923,"nodeType":"ExpressionStatement","src":"6377:28:4"},{"expression":{"id":1928,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"expression":{"id":1924,"name":"bond_","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1910,"src":"6419:5:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond storage pointer"}},"id":1926,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":true,"memberLocation":"6425:7:4","memberName":"balance","nodeType":"MemberAccess","referencedDeclaration":2040,"src":"6419:13:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"hexValue":"30","id":1927,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"6435:1:4","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"},"src":"6419:17:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":1929,"nodeType":"ExpressionStatement","src":"6419:17:4"}]}}]},"documentation":{"id":1899,"nodeType":"StructuredDocumentation","src":"5908:231:4","text":"@dev Internal implementation for debiting a bond\n @param _address The address to debit the bond from\n @param _bond The amount of bond to debit in gwei\n @return bondDebited_ The actual amount debited in gwei"},"implemented":true,"kind":"function","modifiers":[],"name":"_debitBond","nameLocation":"6153:10:4","parameters":{"id":1904,"nodeType":"ParameterList","parameters":[{"constant":false,"id":1901,"mutability":"mutable","name":"_address","nameLocation":"6181:8:4","nodeType":"VariableDeclaration","scope":1947,"src":"6173:16:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":1900,"name":"address","nodeType":"ElementaryTypeName","src":"6173:7:4","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":1903,"mutability":"mutable","name":"_bond","nameLocation":"6207:5:4","nodeType":"VariableDeclaration","scope":1947,"src":"6199:13:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":1902,"name":"uint256","nodeType":"ElementaryTypeName","src":"6199:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"6163:55:4"},"returnParameters":{"id":1907,"nodeType":"ParameterList","parameters":[{"constant":false,"id":1906,"mutability":"mutable","name":"bondDebited_","nameLocation":"6261:12:4","nodeType":"VariableDeclaration","scope":1947,"src":"6253:20:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":1905,"name":"uint256","nodeType":"ElementaryTypeName","src":"6253:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"6252:22:4"},"scope":2034,"stateMutability":"nonpayable","virtual":false,"visibility":"internal"},{"id":1977,"nodeType":"FunctionDefinition","src":"6735:206:4","nodes":[],"body":{"id":1976,"nodeType":"Block","src":"6798:143:4","nodes":[],"statements":[{"assignments":[1957],"declarations":[{"constant":false,"id":1957,"mutability":"mutable","name":"bond_","nameLocation":"6821:5:4","nodeType":"VariableDeclaration","scope":1976,"src":"6808:18:4","stateVariable":false,"storageLocation":"storage","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond"},"typeName":{"id":1956,"nodeType":"UserDefinedTypeName","pathNode":{"id":1955,"name":"Bond","nameLocations":["6808:4:4"],"nodeType":"IdentifierPath","referencedDeclaration":2043,"src":"6808:4:4"},"referencedDeclaration":2043,"src":"6808:4:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond"}},"visibility":"internal"}],"id":1961,"initialValue":{"baseExpression":{"id":1958,"name":"bond","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1542,"src":"6829:4:4","typeDescriptions":{"typeIdentifier":"t_mapping$_t_address_$_t_struct$_Bond_$2043_storage_$","typeString":"mapping(address => struct IBondManager.Bond storage ref)"}},"id":1960,"indexExpression":{"id":1959,"name":"_address","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1950,"src":"6834:8:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"6829:14:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage","typeString":"struct IBondManager.Bond storage ref"}},"nodeType":"VariableDeclarationStatement","src":"6808:35:4"},{"expression":{"id":1969,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"expression":{"id":1962,"name":"bond_","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1957,"src":"6853:5:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond storage pointer"}},"id":1964,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":true,"memberLocation":"6859:7:4","memberName":"balance","nodeType":"MemberAccess","referencedDeclaration":2040,"src":"6853:13:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":1968,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":1965,"name":"bond_","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1957,"src":"6869:5:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage_ptr","typeString":"struct IBondManager.Bond storage pointer"}},"id":1966,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"memberLocation":"6875:7:4","memberName":"balance","nodeType":"MemberAccess","referencedDeclaration":2040,"src":"6869:13:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"+","rightExpression":{"id":1967,"name":"_bond","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1952,"src":"6885:5:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"6869:21:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"6853:37:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":1970,"nodeType":"ExpressionStatement","src":"6853:37:4"},{"eventCall":{"arguments":[{"id":1972,"name":"_address","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1950,"src":"6918:8:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":1973,"name":"_bond","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1952,"src":"6928:5:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":1971,"name":"BondCredited","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":2057,"src":"6905:12:4","typeDescriptions":{"typeIdentifier":"t_function_event_nonpayable$_t_address_$_t_uint256_$returns$__$","typeString":"function (address,uint256)"}},"id":1974,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"6905:29:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":1975,"nodeType":"EmitStatement","src":"6900:34:4"}]},"documentation":{"id":1948,"nodeType":"StructuredDocumentation","src":"6561:169:4","text":"@dev Internal implementation for crediting a bond\n @param _address The address to credit the bond to\n @param _bond The amount of bond to credit in gwei"},"implemented":true,"kind":"function","modifiers":[],"name":"_creditBond","nameLocation":"6744:11:4","parameters":{"id":1953,"nodeType":"ParameterList","parameters":[{"constant":false,"id":1950,"mutability":"mutable","name":"_address","nameLocation":"6764:8:4","nodeType":"VariableDeclaration","scope":1977,"src":"6756:16:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":1949,"name":"address","nodeType":"ElementaryTypeName","src":"6756:7:4","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":1952,"mutability":"mutable","name":"_bond","nameLocation":"6782:5:4","nodeType":"VariableDeclaration","scope":1977,"src":"6774:13:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":1951,"name":"uint256","nodeType":"ElementaryTypeName","src":"6774:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"6755:33:4"},"returnParameters":{"id":1954,"nodeType":"ParameterList","parameters":[],"src":"6798:0:4"},"scope":2034,"stateMutability":"nonpayable","virtual":false,"visibility":"internal"},{"id":2007,"nodeType":"FunctionDefinition","src":"7183:224:4","nodes":[],"body":{"id":2006,"nodeType":"Block","src":"7256:151:4","nodes":[],"statements":[{"assignments":[1988],"declarations":[{"constant":false,"id":1988,"mutability":"mutable","name":"debited","nameLocation":"7274:7:4","nodeType":"VariableDeclaration","scope":2006,"src":"7266:15:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":1987,"name":"uint256","nodeType":"ElementaryTypeName","src":"7266:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"id":1993,"initialValue":{"arguments":[{"id":1990,"name":"_from","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1980,"src":"7295:5:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":1991,"name":"_amount","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1984,"src":"7302:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":1989,"name":"_debitBond","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1947,"src":"7284:10:4","typeDescriptions":{"typeIdentifier":"t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$_t_uint256_$","typeString":"function (address,uint256) returns (uint256)"}},"id":1992,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"7284:26:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"VariableDeclarationStatement","src":"7266:44:4"},{"expression":{"arguments":[{"id":1997,"name":"_to","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1982,"src":"7343:3:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":1998,"name":"debited","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1988,"src":"7348:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":1994,"name":"bondToken","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1530,"src":"7320:9:4","typeDescriptions":{"typeIdentifier":"t_contract$_IERC20_$21865","typeString":"contract IERC20"}},"id":1996,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"7330:12:4","memberName":"safeTransfer","nodeType":"MemberAccess","referencedDeclaration":21959,"src":"7320:22:4","typeDescriptions":{"typeIdentifier":"t_function_internal_nonpayable$_t_contract$_IERC20_$21865_$_t_address_$_t_uint256_$returns$__$attached_to$_t_contract$_IERC20_$21865_$","typeString":"function (contract IERC20,address,uint256)"}},"id":1999,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"7320:36:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":2000,"nodeType":"ExpressionStatement","src":"7320:36:4"},{"eventCall":{"arguments":[{"id":2002,"name":"_from","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1980,"src":"7385:5:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":2003,"name":"debited","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1988,"src":"7392:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":2001,"name":"BondWithdrawn","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":2080,"src":"7371:13:4","typeDescriptions":{"typeIdentifier":"t_function_event_nonpayable$_t_address_$_t_uint256_$returns$__$","typeString":"function (address,uint256)"}},"id":2004,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"7371:29:4","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":2005,"nodeType":"EmitStatement","src":"7366:34:4"}]},"documentation":{"id":1978,"nodeType":"StructuredDocumentation","src":"6947:231:4","text":"@dev Internal implementation for withdrawing funds from a user's bond balance\n @param _from The address whose balance will be reduced\n @param _to The recipient address\n @param _amount The amount to withdraw"},"implemented":true,"kind":"function","modifiers":[],"name":"_withdraw","nameLocation":"7192:9:4","parameters":{"id":1985,"nodeType":"ParameterList","parameters":[{"constant":false,"id":1980,"mutability":"mutable","name":"_from","nameLocation":"7210:5:4","nodeType":"VariableDeclaration","scope":2007,"src":"7202:13:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":1979,"name":"address","nodeType":"ElementaryTypeName","src":"7202:7:4","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":1982,"mutability":"mutable","name":"_to","nameLocation":"7225:3:4","nodeType":"VariableDeclaration","scope":2007,"src":"7217:11:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":1981,"name":"address","nodeType":"ElementaryTypeName","src":"7217:7:4","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":1984,"mutability":"mutable","name":"_amount","nameLocation":"7238:7:4","nodeType":"VariableDeclaration","scope":2007,"src":"7230:15:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":1983,"name":"uint256","nodeType":"ElementaryTypeName","src":"7230:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"7201:45:4"},"returnParameters":{"id":1986,"nodeType":"ParameterList","parameters":[],"src":"7256:0:4"},"scope":2034,"stateMutability":"nonpayable","virtual":false,"visibility":"internal"},{"id":2021,"nodeType":"FunctionDefinition","src":"7591:121:4","nodes":[],"body":{"id":2020,"nodeType":"Block","src":"7666:46:4","nodes":[],"statements":[{"expression":{"expression":{"baseExpression":{"id":2015,"name":"bond","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":1542,"src":"7683:4:4","typeDescriptions":{"typeIdentifier":"t_mapping$_t_address_$_t_struct$_Bond_$2043_storage_$","typeString":"mapping(address => struct IBondManager.Bond storage ref)"}},"id":2017,"indexExpression":{"id":2016,"name":"_address","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":2010,"src":"7688:8:4","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"7683:14:4","typeDescriptions":{"typeIdentifier":"t_struct$_Bond_$2043_storage","typeString":"struct IBondManager.Bond storage ref"}},"id":2018,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"memberLocation":"7698:7:4","memberName":"balance","nodeType":"MemberAccess","referencedDeclaration":2040,"src":"7683:22:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"functionReturnParameters":2014,"id":2019,"nodeType":"Return","src":"7676:29:4"}]},"documentation":{"id":2008,"nodeType":"StructuredDocumentation","src":"7413:173:4","text":"@dev Internal implementation for getting the bond balance\n @param _address The address to get the bond balance for\n @return The bond balance of the address"},"implemented":true,"kind":"function","modifiers":[],"name":"_getBondBalance","nameLocation":"7600:15:4","parameters":{"id":2011,"nodeType":"ParameterList","parameters":[{"constant":false,"id":2010,"mutability":"mutable","name":"_address","nameLocation":"7624:8:4","nodeType":"VariableDeclaration","scope":2021,"src":"7616:16:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":2009,"name":"address","nodeType":"ElementaryTypeName","src":"7616:7:4","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"7615:18:4"},"returnParameters":{"id":2014,"nodeType":"ParameterList","parameters":[{"constant":false,"id":2013,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":2021,"src":"7657:7:4","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":2012,"name":"uint256","nodeType":"ElementaryTypeName","src":"7657:7:4","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"7656:9:4"},"scope":2034,"stateMutability":"view","virtual":false,"visibility":"internal"},{"id":2023,"nodeType":"ErrorDefinition","src":"7875:25:4","nodes":[],"errorSelector":"e92c469f","name":"InsufficientBond","nameLocation":"7881:16:4","parameters":{"id":2022,"nodeType":"ParameterList","parameters":[],"src":"7897:2:4"}},{"id":2025,"nodeType":"ErrorDefinition","src":"7905:25:4","nodes":[],"errorSelector":"9c8d2cd2","name":"InvalidRecipient","nameLocation":"7911:16:4","parameters":{"id":2024,"nodeType":"ParameterList","parameters":[],"src":"7927:2:4"}},{"id":2027,"nodeType":"ErrorDefinition","src":"7935:28:4","nodes":[],"errorSelector":"87da3304","name":"MustMaintainMinBond","nameLocation":"7941:19:4","parameters":{"id":2026,"nodeType":"ParameterList","parameters":[],"src":"7960:2:4"}},{"id":2029,"nodeType":"ErrorDefinition","src":"7968:25:4","nodes":[],"errorSelector":"4555262b","name":"NoBondToWithdraw","nameLocation":"7974:16:4","parameters":{"id":2028,"nodeType":"ParameterList","parameters":[],"src":"7990:2:4"}},{"id":2031,"nodeType":"ErrorDefinition","src":"7998:30:4","nodes":[],"errorSelector":"87bdc6a9","name":"NoWithdrawalRequested","nameLocation":"8004:21:4","parameters":{"id":2030,"nodeType":"ParameterList","parameters":[],"src":"8025:2:4"}},{"id":2033,"nodeType":"ErrorDefinition","src":"8033:35:4","nodes":[],"errorSelector":"fb52063b","name":"WithdrawalAlreadyRequested","nameLocation":"8039:26:4","parameters":{"id":2032,"nodeType":"ParameterList","parameters":[],"src":"8065:2:4"}}],"abstract":false,"baseContracts":[{"baseName":{"id":1516,"name":"EssentialContract","nameLocations":["522:17:4"],"nodeType":"IdentifierPath","referencedDeclaration":4802,"src":"522:17:4"},"id":1517,"nodeType":"InheritanceSpecifier","src":"522:17:4"},{"baseName":{"id":1518,"name":"IBondManager","nameLocations":["541:12:4"],"nodeType":"IdentifierPath","referencedDeclaration":2159,"src":"541:12:4"},"id":1519,"nodeType":"InheritanceSpecifier","src":"541:12:4"}],"canonicalName":"BondManager","contractDependencies":[],"contractKind":"contract","documentation":{"id":1515,"nodeType":"StructuredDocumentation","src":"345:153:4","text":"@title BondManager\n @notice L1 implementation of BondManager with time-based withdrawal mechanism\n @custom:security-contact security@taiko.xyz"},"fullyImplemented":true,"linearizedBaseContracts":[2034,2159,4802,11319,11452,16421,11748,21013,20512,20161,20171],"name":"BondManager","nameLocation":"507:11:4","scope":2035,"usedErrors":[2023,2025,2027,2029,2031,2033,4337,4339,4341,4343,4345,4347],"usedEvents":[2050,2057,2064,2073,2080,2087,2092,4330,4335,11230,11337,11594,20148,20155,20160]}],"license":"MIT"},"id":4} \ No newline at end of file diff --git a/shasta/src/l2/bindings.rs b/shasta/src/l2/bindings.rs new file mode 100644 index 00000000..06a78909 --- /dev/null +++ b/shasta/src/l2/bindings.rs @@ -0,0 +1,17 @@ +#![allow(clippy::too_many_arguments)] + +use alloy::sol; + +sol!( + #[allow(missing_docs)] + #[sol(rpc)] + BondManager, + "src/l2/abi/BondManager.json" +); + +sol!( + #[allow(missing_docs)] + #[sol(rpc, all_derives)] + Anchor, + "src/l2/abi/Anchor.json" +); diff --git a/shasta/src/l2/execution_layer.rs b/shasta/src/l2/execution_layer.rs index 7ea00e43..ee99912e 100644 --- a/shasta/src/l2/execution_layer.rs +++ b/shasta/src/l2/execution_layer.rs @@ -1,30 +1,44 @@ +use crate::l2::bindings::{Anchor, ICheckpointStore::Checkpoint}; +use crate::shared_abi::bindings::{ + Bridge::{self, MessageSent}, + IBridge::Message, + SignalSent, +}; use alloy::{ consensus::{ BlockHeader, SignableTransaction, Transaction as AnchorTransaction, TxEnvelope, transaction::Recovered, }, eips::BlockNumberOrTag, - primitives::{Address, B256}, + primitives::{Address, B256, Bytes, FixedBytes}, providers::{DynProvider, Provider}, rpc::types::Transaction, - signers::Signature, + signers::{Signature, Signer as AlloySigner}, + sol_types::SolEvent, }; use anyhow::Error; -use common::crypto::{GOLDEN_TOUCH_ADDRESS, GOLDEN_TOUCH_PRIVATE_KEY}; use common::shared::{ alloy_tools, execution_layer::ExecutionLayer as ExecutionLayerCommon, l2_slot_info_v2::L2SlotInfoV2, }; +use common::{ + crypto::{GOLDEN_TOUCH_ADDRESS, GOLDEN_TOUCH_PRIVATE_KEY}, + signer::Signer, +}; use pacaya::l2::config::TaikoConfig; -use taiko_bindings::anchor::{Anchor, ICheckpointStore::Checkpoint}; +use serde_json::Value; +use std::sync::Arc; use tracing::{debug, info}; -use serde_json::Value; pub struct L2ExecutionLayer { common: ExecutionLayerCommon, - provider: DynProvider, + pub provider: DynProvider, shasta_anchor: Anchor::AnchorInstance, + pub bridge: Bridge::BridgeInstance, + pub signal_service: Address, + pub chain_id: u64, pub config: TaikoConfig, + l2_call_signer: Arc, } impl L2ExecutionLayer { @@ -37,12 +51,30 @@ impl L2ExecutionLayer { let common = ExecutionLayerCommon::new(provider.clone(), taiko_config.signer.get_address()).await?; - info!("L2 chain ID {}", common.chain_id()); + let chain_id = common.chain_id(); + info!("L2 chain ID {}", chain_id); + + // Surge: Store the bridge for processing L2 calls + let chain_id_string = format!("{}", chain_id); + let zeros_needed = 38usize.saturating_sub(chain_id_string.len()); + let bridge_address: Address = + format!("0x{}{}01", chain_id_string, "0".repeat(zeros_needed)).parse()?; + let bridge = Bridge::new(bridge_address, provider.clone()); + + // Signal service address (same format as bridge, but ending in 05) + let signal_service: Address = + format!("0x{}{}05", chain_id_string, "0".repeat(zeros_needed)).parse()?; + + let l2_call_signer = taiko_config.signer.clone(); Ok(Self { common, provider, shasta_anchor, + bridge, + signal_service, + chain_id, + l2_call_signer, config: taiko_config, }) } @@ -54,7 +86,7 @@ impl L2ExecutionLayer { pub async fn construct_anchor_tx( &self, l2_slot_info: &L2SlotInfoV2, - anchor_block_params: Checkpoint, + anchor_block_params: (Checkpoint, Vec>), ) -> Result { let nonce = self .provider @@ -68,7 +100,7 @@ impl L2ExecutionLayer { let call_builder = self .shasta_anchor - .anchorV4(anchor_block_params) + .anchorV4WithSignalSlots(anchor_block_params.0, anchor_block_params.1) .gas(1_000_000) // value expected by Taiko .max_fee_per_gas(u128::from(l2_slot_info.base_fee())) // value expected by Taiko .max_priority_fee_per_gas(0) // value expected by Taiko @@ -202,12 +234,12 @@ impl L2ExecutionLayer { } pub fn decode_anchor_id_from_tx_data(data: &[u8]) -> Result { - match ::abi_decode_validate(data) { + match ::abi_decode_validate(data) { Ok(tx_data) => Ok(tx_data._checkpoint.blockNumber.to::()), Err(v4_error) => { let tx_data = ::abi_decode_validate(data) .map_err(|v3_error| anyhow::anyhow!( - "Failed to decode anchor id from tx data as anchorV4 ({}) or anchorV3 ({}).", + "Failed to decode anchor id from tx data as anchorV4WithSignalSlots ({}) or anchorV3 ({}).", v4_error, v3_error ))?; @@ -216,9 +248,9 @@ impl L2ExecutionLayer { } } - pub fn get_anchor_tx_data(data: &[u8]) -> Result { + pub fn get_anchor_tx_data(data: &[u8]) -> Result { let tx_data = - ::abi_decode_validate(data) + ::abi_decode_validate(data) .map_err(|e| anyhow::anyhow!("Failed to decode anchor tx data: {}", e))?; Ok(tx_data) } @@ -264,8 +296,155 @@ impl L2ExecutionLayer { pub fn decode_block_params_from_tx_data(data: &[u8]) -> Result { let tx_data = - ::abi_decode_validate(data) + ::abi_decode_validate(data) .map_err(|e| anyhow::anyhow!("Failed to decode proposal id from tx data: {}", e))?; Ok(tx_data._checkpoint) } } + +// Surge: L2 EL ops for Bridge Handler + +#[allow(async_fn_in_trait)] +pub trait L2BridgeHandlerOps { + // Surge: Builds the L2 call expected to be initiated an L1 contract via the Bridge + // This is initially sent as a user op to the bridge handler RPC + async fn construct_l2_call_tx(&self, message: Message) -> Result; + + // Surge: This can be made to retrieve multiple signal slots + async fn find_message_and_signal_slot( + &self, + block_id: u64, + ) -> Result)>, anyhow::Error>; +} + +impl L2BridgeHandlerOps for L2ExecutionLayer { + async fn construct_l2_call_tx(&self, message: Message) -> Result { + use alloy::signers::local::PrivateKeySigner; + use std::str::FromStr; + + debug!("Constructing bridge call transaction for L2 call"); + + let signer_address = self.l2_call_signer.get_address(); + + let nonce = self + .provider + .get_transaction_count(signer_address) + .await + .map_err(|e| anyhow::anyhow!("Failed to get nonce for bridge call: {}", e))?; + + let call_builder = self + .bridge + .processMessage(message, Bytes::new()) + .gas(1_000_000) + .max_fee_per_gas(1_000_000_000) // 1 gwei + .max_priority_fee_per_gas(0) + .nonce(nonce) + .chain_id(self.chain_id); + + let typed_tx = call_builder + .into_transaction_request() + .build_typed_tx() + .map_err(|_| anyhow::anyhow!("L2 Call Tx: Failed to build typed transaction"))?; + + let tx_eip1559 = typed_tx + .eip1559() + .ok_or_else(|| anyhow::anyhow!("L2 Call Tx: Failed to extract EIP-1559 transaction"))? + .clone(); + + // Sign the transaction using the L2 call signer + let signature = match self.l2_call_signer.as_ref() { + Signer::Web3signer(web3signer, address) => { + let signature_bytes = web3signer.sign_transaction(&tx_eip1559, *address).await?; + Signature::try_from(signature_bytes.as_slice()) + .map_err(|e| anyhow::anyhow!("Failed to parse signature: {}", e))? + } + Signer::PrivateKey(private_key, _) => { + let signer = PrivateKeySigner::from_str(private_key.as_str())?; + AlloySigner::sign_hash(&signer, &tx_eip1559.signature_hash()).await? + } + }; + + let sig_tx = tx_eip1559.into_signed(signature); + + let tx_envelope = TxEnvelope::from(sig_tx); + + debug!("L2 Call transaction hash: {}", tx_envelope.tx_hash()); + + let tx = Transaction { + inner: Recovered::new_unchecked(tx_envelope, signer_address), + block_hash: None, + block_number: None, + transaction_index: None, + effective_gas_price: None, + }; + Ok(tx) + } + + async fn find_message_and_signal_slot( + &self, + block_id: u64, + ) -> Result)>, anyhow::Error> { + use alloy::rpc::types::Filter; + + let bridge_address = *self.bridge.address(); + let signal_service_address = self.signal_service; + + let filter = Filter::new().from_block(block_id).to_block(block_id); + + // Get logs from the bridge contract (MessageSent event) + let bridge_filter = filter + .clone() + .address(bridge_address) + .event_signature(MessageSent::SIGNATURE_HASH); + + let bridge_logs = self + .provider + .get_logs(&bridge_filter) + .await + .map_err(|e| anyhow::anyhow!("Failed to get MessageSent logs from bridge: {e}"))?; + + // Get logs from the signal service contract (SignalSent event) + let signal_filter = filter + .address(signal_service_address) + .event_signature(SignalSent::SIGNATURE_HASH); + + let signal_logs = self.provider.get_logs(&signal_filter).await.map_err(|e| { + anyhow::anyhow!("Failed to get SignalSent logs from signal service: {e}") + })?; + + // Check if both events are present + if bridge_logs.is_empty() || signal_logs.is_empty() { + return Ok(None); + } + + // Decode MessageSent event + let message = { + let log = bridge_logs + .first() + .ok_or_else(|| anyhow::anyhow!("bridge_logs is empty despite non-empty check"))?; + let log_data = alloy::primitives::LogData::new_unchecked( + log.topics().to_vec(), + log.data().data.clone(), + ); + MessageSent::decode_log_data(&log_data) + .map_err(|e| anyhow::anyhow!("Failed to decode MessageSent event: {e}"))? + .message + }; + + // Decode SignalSent event + let slot = { + let log = signal_logs + .first() + .ok_or_else(|| anyhow::anyhow!("signal_logs is empty despite non-empty check"))?; + let log_data = alloy::primitives::LogData::new_unchecked( + log.topics().to_vec(), + log.data().data.clone(), + ); + SignalSent::decode_log_data(&log_data) + .map_err(|e| anyhow::anyhow!("Failed to decode SignalSent event: {e}"))? + .slot + }; + + Ok(Some((message, slot))) + } +} diff --git a/shasta/src/l2/mod.rs b/shasta/src/l2/mod.rs index 79cfa364..a34cdfda 100644 --- a/shasta/src/l2/mod.rs +++ b/shasta/src/l2/mod.rs @@ -1,3 +1,4 @@ +pub mod bindings; pub mod execution_layer; pub mod extra_data; pub mod taiko; diff --git a/shasta/src/l2/taiko.rs b/shasta/src/l2/taiko.rs index 87fe7d1a..f3fb65f1 100644 --- a/shasta/src/l2/taiko.rs +++ b/shasta/src/l2/taiko.rs @@ -1,6 +1,7 @@ use super::execution_layer::L2ExecutionLayer; use crate::forced_inclusion::InboxForcedInclusionState; use crate::l1::protocol_config::ProtocolConfig; +use crate::l2::bindings::Anchor; use alloy::{ consensus::BlockHeader, eips::BlockNumberOrTag, @@ -21,7 +22,6 @@ use common::{ use pacaya::l2::config::TaikoConfig; use std::sync::Arc; use taiko_alethia_reth::validation::ANCHOR_V3_V4_GAS_LIMIT; -use taiko_bindings::anchor::Anchor; use taiko_bindings::inbox::IInbox::Config; use taiko_protocol::shasta::constants::min_base_fee_for_chain; use tracing::{debug, trace}; @@ -295,7 +295,7 @@ impl Taiko { L2ExecutionLayer::decode_anchor_id_from_tx_data(data) } - pub fn get_anchor_tx_data(data: &[u8]) -> Result { + pub fn get_anchor_tx_data(data: &[u8]) -> Result { L2ExecutionLayer::get_anchor_tx_data(data) } diff --git a/shasta/src/lib.rs b/shasta/src/lib.rs index 32d9dcb0..57070ad8 100644 --- a/shasta/src/lib.rs +++ b/shasta/src/lib.rs @@ -4,6 +4,7 @@ pub mod forced_inclusion; pub mod l1; pub mod l2; mod node; +mod shared_abi; pub use node::proposal_manager::block_advancer::BlockAdvancer; pub use node::proposal_manager::l2_block_payload::L2BlockV2Payload; diff --git a/shasta/src/node/block_advancer.rs b/shasta/src/node/block_advancer.rs index cb2b656f..f08e57d6 100644 --- a/shasta/src/node/block_advancer.rs +++ b/shasta/src/node/block_advancer.rs @@ -1,4 +1,5 @@ use crate::l1::protocol_config::ProtocolConfig; +use crate::l2::bindings::ICheckpointStore::Checkpoint; use crate::l2::execution_layer::L2ExecutionLayer; use crate::node::proposal_manager::block_advancer::BlockAdvancer; use crate::node::proposal_manager::l2_block_payload::L2BlockV2Payload; @@ -13,7 +14,6 @@ use std::future::Future; use std::pin::Pin; use std::sync::Arc; use taiko_alethia_reth::validation::ANCHOR_V3_V4_GAS_LIMIT; -use taiko_bindings::anchor::ICheckpointStore::Checkpoint; pub struct ShastaBlockAdvancer { l2_execution_layer: Arc, @@ -48,11 +48,14 @@ impl BlockAdvancer for ShastaBlockAdvancer { l2_block_payload.tx_list.len() ); - let anchor_block_params = Checkpoint { - blockNumber: l2_block_payload.anchor_block_id.try_into()?, - blockHash: l2_block_payload.anchor_block_hash, - stateRoot: l2_block_payload.anchor_state_root, - }; + let anchor_block_params = ( + Checkpoint { + blockNumber: l2_block_payload.anchor_block_id.try_into()?, + blockHash: l2_block_payload.anchor_block_hash, + stateRoot: l2_block_payload.anchor_state_root, + }, + vec![], // No signal slots for standard block advancement + ); let anchor_tx = self .l2_execution_layer diff --git a/shasta/src/node/mod.rs b/shasta/src/node/mod.rs index e2d6a1c5..092f0046 100644 --- a/shasta/src/node/mod.rs +++ b/shasta/src/node/mod.rs @@ -303,12 +303,19 @@ impl Node { .proposal_manager .should_new_block_be_created(&pending_tx_list, &l2_slot_ctx) { - let preconfed_block = self - .proposal_manager - .preconfirm_block(pending_tx_list, &l2_slot_ctx) - .await?; + // Surge: preconfirm only when there are pending transactions or user ops + if pending_tx_list + .as_ref() + .is_some_and(|pre_built_list| !pre_built_list.tx_list.is_empty()) + || self.proposal_manager.has_pending_user_ops().await + { + let preconfed_block = self + .proposal_manager + .preconfirm_block(pending_tx_list, &l2_slot_ctx) + .await?; - self.verify_preconfed_block(preconfed_block).await?; + self.verify_preconfed_block(preconfed_block).await?; + } } } diff --git a/shasta/src/node/proposal_manager/batch_builder.rs b/shasta/src/node/proposal_manager/batch_builder.rs new file mode 100644 index 00000000..f88e759f --- /dev/null +++ b/shasta/src/node/proposal_manager/batch_builder.rs @@ -0,0 +1,659 @@ +use super::proposal::Proposals; +use crate::l1::execution_layer::ExecutionLayer; +use crate::l2::bindings::ICheckpointStore::Checkpoint; +use crate::metrics::Metrics; +use crate::node::proposal_manager::{ + bridge_handler::{L1Call, UserOp, UserOpStatus, UserOpStatusStore}, + l2_block_payload::L2BlockV2Payload, + proposal::Proposal, +}; +use alloy::primitives::{Address, FixedBytes}; +use anyhow::Error; +use common::shared::l2_tx_lists::PreBuiltTxList; +use common::{ + batch_builder::BatchBuilderConfig, + shared::l2_block_v2::{L2BlockV2, L2BlockV2Draft}, +}; +use common::{ + l1::{ethereum_l1::EthereumL1, slot_clock::SlotClock, transaction_error::TransactionError}, + shared::anchor_block_info::AnchorBlockInfo, +}; +use std::{collections::VecDeque, sync::Arc}; +use tracing::{debug, info, trace, warn}; + +#[allow(dead_code)] +pub struct BatchBuilder { + config: BatchBuilderConfig, + proposals_to_send: VecDeque, + current_proposal: Option, + slot_clock: Arc, + metrics: Arc, +} + +#[allow(dead_code)] +impl BatchBuilder { + pub fn new( + config: BatchBuilderConfig, + slot_clock: Arc, + metrics: Arc, + ) -> Self { + Self { + config, + proposals_to_send: VecDeque::new(), + current_proposal: None, + slot_clock, + metrics, + } + } + + pub fn get_config(&self) -> &BatchBuilderConfig { + &self.config + } + + // TODO use L2BlockV2 here + pub fn can_consume_l2_block(&mut self, l2_draft_block: &L2BlockV2Draft) -> bool { + let is_time_shift_expired = self.is_time_shift_expired(l2_draft_block.timestamp_sec); + self.current_proposal.as_mut().is_some_and(|batch| { + let new_block_count = match u16::try_from(batch.l2_blocks.len() + 1) { + Ok(n) => n, + Err(_) => return false, + }; + + let mut new_total_bytes = + batch.total_bytes + l2_draft_block.prebuilt_tx_list.bytes_length; + + if !self.config.is_within_bytes_limit(new_total_bytes) { + // first compression, compressing the batch without the new L2 block + batch.compress(); + new_total_bytes = batch.total_bytes + l2_draft_block.prebuilt_tx_list.bytes_length; + if !self.config.is_within_bytes_limit(new_total_bytes) { + // second compression, compressing the batch with the new L2 block + // we can tolerate the processing overhead as it's a very rare case + let start = std::time::Instant::now(); + let mut batch_clone = batch.clone(); + batch_clone.add_l2_draft_block(l2_draft_block.clone()); + batch_clone.compress(); + new_total_bytes = batch_clone.total_bytes; + debug!( + "can_consume_l2_block: Second compression took {} ms, new total bytes: {}", + start.elapsed().as_millis(), + new_total_bytes + ); + } + } + + self.config.is_within_bytes_limit(new_total_bytes) + && self.config.is_within_block_limit(new_block_count) + && !is_time_shift_expired + }) + } + + pub fn current_proposal_is_empty(&self) -> bool { + self.current_proposal + .as_ref() + .is_none_or(|b| b.l2_blocks.is_empty()) + } + + pub fn create_new_batch(&mut self, id: u64, anchor_block: AnchorBlockInfo) { + self.finalize_current_batch(); + + self.current_proposal = Some(Proposal { + id, + l2_blocks: vec![], + total_bytes: 0, + coinbase: self.config.default_coinbase, + anchor_block_id: anchor_block.id(), + anchor_block_timestamp_sec: anchor_block.timestamp_sec(), + anchor_block_hash: anchor_block.hash(), + anchor_state_root: anchor_block.state_root(), + num_forced_inclusion: 0, + created_at_sec: 0, + pending_confirmation: false, + checkpoint: Checkpoint::default(), + user_ops: vec![], + signal_slots: vec![], + l1_calls: vec![], + }); + } + + pub fn add_l2_draft_block( + &mut self, + l2_draft_block: L2BlockV2Draft, + ) -> Result { + if let Some(current_proposal) = self.current_proposal.as_mut() { + let payload = current_proposal.add_l2_draft_block(l2_draft_block); + + debug!( + "Added L2 draft block to batch: l2 blocks: {}, total bytes: {}", + current_proposal.l2_blocks.len(), + current_proposal.total_bytes + ); + Ok(payload) + } else { + Err(anyhow::anyhow!("No current batch")) + } + } + + pub fn add_fi_block( + &mut self, + fi_block: L2BlockV2Draft, + anchor_params: Checkpoint, + ) -> Result { + if let Some(current_proposal) = self.current_proposal.as_mut() { + let payload = current_proposal.add_forced_inclusion(fi_block, anchor_params); + + debug!( + "Added forced inclusion L2 draft block to batch: l2 blocks: {}, total bytes: {}", + current_proposal.l2_blocks.len(), + current_proposal.total_bytes + ); + Ok(payload) + } else { + Err(anyhow::anyhow!("No current batch")) + } + } + + pub fn add_l2_block_and_get_current_proposal( + &mut self, + l2_block: L2BlockV2, + ) -> Result<&Proposal, Error> { + if let Some(current_proposal) = self.current_proposal.as_mut() { + current_proposal.add_l2_block(l2_block); + + debug!( + "Added L2 block to batch: l2 blocks: {}, total bytes: {}", + current_proposal.l2_blocks.len(), + current_proposal.total_bytes + ); + Ok(current_proposal) + } else { + Err(anyhow::anyhow!("No current batch")) + } + } + + // Surge: adds user ops that initiate L2 calls + pub fn add_user_op(&mut self, user_op_data: UserOp) -> Result<&Proposal, Error> { + if let Some(current_proposal) = self.current_proposal.as_mut() { + current_proposal.user_ops.push(user_op_data.clone()); + + info!("Added user op: {:?}", user_op_data); + Ok(current_proposal) + } else { + Err(anyhow::anyhow!("No current batch")) + } + } + + // Surge: adds signal slots to make same slot L2 call valid + pub fn add_signal_slot(&mut self, signal_slot: FixedBytes<32>) -> Result<&Proposal, Error> { + if let Some(current_proposal) = self.current_proposal.as_mut() { + current_proposal.signal_slots.push(signal_slot); + + info!("Added signal slot: {:?}", signal_slot); + Ok(current_proposal) + } else { + Err(anyhow::anyhow!("No current batch")) + } + } + + // Surge: adds L1 calls initiated by L2 contracts + pub fn add_l1_call(&mut self, l1_call: L1Call) -> Result<&Proposal, Error> { + if let Some(current_proposal) = self.current_proposal.as_mut() { + current_proposal.l1_calls.push(l1_call.clone()); + + info!("Added L1 call: {:?}", l1_call); + Ok(current_proposal) + } else { + Err(anyhow::anyhow!("No current batch")) + } + } + + // Surge: Sets the proposal checkpoint + // This is called at the end of sequencing + pub fn set_proposal_checkpoint(&mut self, checkpoint: Checkpoint) -> Result<&Proposal, Error> { + if let Some(current_proposal) = self.current_proposal.as_mut() { + current_proposal.checkpoint = checkpoint.clone(); + + debug!("Update proposal checkpoint: {:?}", checkpoint); + Ok(current_proposal) + } else { + Err(anyhow::anyhow!("No current batch")) + } + } + + pub fn get_current_proposal_last_block_timestamp(&self) -> Option { + self.current_proposal + .as_ref() + .and_then(|p| p.l2_blocks.last().map(|b| b.timestamp_sec)) + } + + pub fn remove_last_l2_block(&mut self) { + if let Some(current_proposal) = self.current_proposal.as_mut() { + let removed_block = current_proposal.l2_blocks.pop(); + if let Some(removed_block) = removed_block { + current_proposal.total_bytes -= removed_block.prebuilt_tx_list.bytes_length; + if current_proposal.l2_blocks.is_empty() { + self.current_proposal = None; + } + debug!( + "Removed L2 block from batch: {} txs, {} bytes", + removed_block.prebuilt_tx_list.tx_list.len(), + removed_block.prebuilt_tx_list.bytes_length + ); + } + } + } + + #[allow(clippy::too_many_arguments)] + pub async fn recover_from( + &mut self, + proposal_id: u64, + anchor_info: AnchorBlockInfo, + coinbase: Address, + tx_list: Vec, + l2_block_timestamp_sec: u64, + gas_limit: u64, + is_forced_inclusion: bool, + ) -> Result<(), Error> { + // We have a new proposal when proposal ID differs + // Otherwise we continue with the current proposal + if !self.is_same_proposal_id(proposal_id) { + self.finalize_current_batch(); + debug!( + "Creating new proposal during recovery: proposal_id {}, anchor_block_id {} coinbase {}", + proposal_id, + anchor_info.id(), + coinbase + ); + self.current_proposal = Some(Proposal { + id: proposal_id, + total_bytes: 0, + l2_blocks: vec![], + coinbase, + anchor_block_id: anchor_info.id(), + anchor_block_timestamp_sec: anchor_info.timestamp_sec(), + anchor_block_hash: anchor_info.hash(), + anchor_state_root: anchor_info.state_root(), + num_forced_inclusion: 0, + created_at_sec: 0, + pending_confirmation: false, + checkpoint: Checkpoint::default(), + // Surge: This is NOT OK for recovery, but fine for the POC. + user_ops: vec![], + signal_slots: vec![], + l1_calls: vec![], + }); + } + + if is_forced_inclusion { + if let Some(batch) = self.current_proposal.as_ref() + && !batch.l2_blocks.is_empty() + { + return Err(anyhow::anyhow!( + "recover_from: Cannot add forced inclusion L2 block to non-empty proposal" + )); + } + + self.inc_forced_inclusion()?; + } else { + if let Some(batch) = self.current_proposal.as_mut() + && batch.anchor_block_id < anchor_info.id() + { + batch.anchor_block_id = anchor_info.id(); + batch.anchor_block_timestamp_sec = anchor_info.timestamp_sec(); + batch.anchor_block_hash = anchor_info.hash(); + batch.anchor_state_root = anchor_info.state_root(); + } + + let bytes_length = + common::shared::l2_tx_lists::encode_and_compress(&tx_list)?.len() as u64; + + let l2_block = L2BlockV2::new_from( + common::shared::l2_tx_lists::PreBuiltTxList { + tx_list, + estimated_gas_used: 0, + bytes_length, + }, + l2_block_timestamp_sec, + coinbase, + anchor_info.id(), + gas_limit, + ); + + // TODO we add block to the current proposal. + // But we should verify that it fit N blob data size + // Otherwise we should do a reorg + // TODO align on blob count with all teams + + // at previous step we check that proposal exists + self.add_l2_block_and_get_current_proposal(l2_block)?; + } + Ok(()) + } + + fn is_same_proposal_id(&self, proposal_id: u64) -> bool { + // Note: proposal.id is not part of BatchLike trait, so we need to access it directly + // Since Proposal has a public id field, we can access it + self.current_proposal + .as_ref() + .is_some_and(|proposal| proposal.id == proposal_id) + } + + pub fn is_empty(&self) -> bool { + trace!( + "batch_builder::is_empty: current_proposal is none: {}, proposals_to_send len: {}", + self.current_proposal.is_none(), + self.proposals_to_send.len() + ); + self.current_proposal.is_none() && self.proposals_to_send.is_empty() + } + + pub async fn try_submit_oldest_batch( + &mut self, + ethereum_l1: Arc>, + submit_only_full_batches: bool, + status_store: Option, + ) -> Result<(), Error> { + if self.current_proposal.is_some() + && (!submit_only_full_batches + || !self.config.is_within_block_limit( + u16::try_from( + self.current_proposal + .as_ref() + .map(|b| b.l2_blocks.len()) + .unwrap_or(0), + )? + 1, + )) + { + self.finalize_current_batch(); + } + + if let Some(batch) = self.proposals_to_send.front() { + if ethereum_l1 + .execution_layer + .transaction_monitor + .is_transaction_in_progress() + .await? + { + debug!( + proposals_to_send = %self.proposals_to_send.len(), + current_proposal = %self.current_proposal.is_some(), + "Cannot submit batch, transaction is in progress.", + ); + return Err(anyhow::anyhow!( + "Cannot submit batch, transaction is in progress." + )); + } + + debug!( + anchor_block_id = %batch.anchor_block_id, + coinbase = %batch.coinbase, + l2_blocks_len = %batch.l2_blocks.len(), + total_bytes = %batch.total_bytes, + proposals_to_send = %self.proposals_to_send.len(), + current_proposal = %self.current_proposal.is_some(), + "Submitting batch" + ); + + debug!("Checkpoint data before proposing: {:?}", &batch.checkpoint); + + // Collect user op IDs from the batch being submitted + let user_op_ids: Vec = batch.user_ops.iter().map(|op| op.id).collect(); + let has_user_ops = !user_op_ids.is_empty() && status_store.is_some(); + + // Create notification channels if there are user ops to track + let (tx_hash_sender, tx_hash_receiver) = if has_user_ops { + let (s, r) = tokio::sync::oneshot::channel(); + (Some(s), Some(r)) + } else { + (None, None) + }; + let (tx_result_sender, tx_result_receiver) = if has_user_ops { + let (s, r) = tokio::sync::oneshot::channel(); + (Some(s), Some(r)) + } else { + (None, None) + }; + + if let Err(err) = ethereum_l1 + .execution_layer + // TODO send a Proosal to function + .send_proposal_to_l1(batch.clone(), tx_hash_sender, tx_result_sender) + .await + { + // Reject all user ops in failed batches + if let Some(ref store) = status_store { + let reason = format!("L1 multicall failed: {}", err); + for batch in &self.proposals_to_send { + for op in &batch.user_ops { + store.set( + op.id, + &UserOpStatus::Rejected { + reason: reason.clone(), + }, + ); + } + } + } + + if let Some(transaction_error) = err.downcast_ref::() + && !matches!(transaction_error, TransactionError::EstimationTooEarly) + { + debug!("BatchBuilder: Transaction error, removing all batches"); + self.proposals_to_send.clear(); + } + return Err(err); + } + + // Spawn background task to track user op status through Processing → Executed/Rejected + if let (Some(hash_rx), Some(result_rx), Some(store)) = + (tx_hash_receiver, tx_result_receiver, status_store) + { + tokio::spawn(async move { + // Wait for tx hash → set Processing + let tx_hash = match hash_rx.await { + Ok(tx_hash) => { + for id in &user_op_ids { + store.set(*id, &UserOpStatus::Processing { tx_hash }); + } + Some(tx_hash) + } + Err(_) => { + for id in &user_op_ids { + store.set( + *id, + &UserOpStatus::Rejected { + reason: "Transaction failed to send".to_string(), + }, + ); + } + None + } + }; + + // Wait for confirmation result → set Executed or Rejected + if tx_hash.is_some() { + match result_rx.await { + Ok(true) => { + for id in &user_op_ids { + store.set(*id, &UserOpStatus::Executed); + } + } + Ok(false) => { + for id in &user_op_ids { + store.set( + *id, + &UserOpStatus::Rejected { + reason: "L1 multicall reverted".to_string(), + }, + ); + } + } + Err(_) => { + for id in &user_op_ids { + store.set( + *id, + &UserOpStatus::Rejected { + reason: "Transaction monitor dropped".to_string(), + }, + ); + } + } + } + } + }); + } + + self.proposals_to_send.pop_front(); + } + + Ok(()) + } + + // TODO do we have that check in SC? + pub fn is_time_shift_expired(&self, current_l2_slot_timestamp: u64) -> bool { + if let Some(current_proposal) = self.current_proposal.as_ref() + && let Some(last_block) = current_proposal.l2_blocks.last() + { + return current_l2_slot_timestamp - last_block.timestamp_sec + > self.config.max_time_shift_between_blocks_sec; + } + false + } + // TODO do we have that check in SC? + pub fn is_time_shift_between_blocks_expiring(&self, current_l2_slot_timestamp: u64) -> bool { + if let Some(current_proposal) = self.current_proposal.as_ref() { + // l1_batches is not empty + if let Some(last_block) = current_proposal.l2_blocks.last() { + if current_l2_slot_timestamp < last_block.timestamp_sec { + warn!("Preconfirmation timestamp is before the last block timestamp"); + return false; + } + // is the last L1 slot to add an empty L2 block so we don't have a time shift overflow + return self.is_the_last_l1_slot_to_add_an_empty_l2_block( + current_l2_slot_timestamp, + last_block.timestamp_sec, + ); + } + } + false + } + // TODO do we have that check in SC? + fn is_the_last_l1_slot_to_add_an_empty_l2_block( + &self, + current_l2_slot_timestamp: u64, + last_block_timestamp: u64, + ) -> bool { + current_l2_slot_timestamp - last_block_timestamp + >= self.config.max_time_shift_between_blocks_sec - self.config.l1_slot_duration_sec + } + + pub fn is_greater_than_max_anchor_height_offset(&self) -> Result { + if let Some(current_proposal) = self.current_proposal.as_ref() { + let slots_since_l1_block = self + .slot_clock + .slots_since_l1_block(current_proposal.anchor_block_timestamp_sec)?; + return Ok(slots_since_l1_block > self.config.max_anchor_height_offset); + } + Ok(false) + } + + fn is_empty_block_required(&self, preconfirmation_timestamp: u64) -> bool { + self.is_time_shift_between_blocks_expiring(preconfirmation_timestamp) + } + + pub fn clone_without_batches(&self) -> Self { + Self { + config: self.config.clone(), + proposals_to_send: VecDeque::new(), + current_proposal: None, + slot_clock: self.slot_clock.clone(), + metrics: self.metrics.clone(), + } + } + + pub fn get_number_of_batches(&self) -> u64 { + self.proposals_to_send.len() as u64 + + if self.current_proposal.is_some() { + 1 + } else { + 0 + } + } + + /// Alias for `take_proposals_to_send` for compatibility + pub fn take_batches_to_send(&mut self) -> VecDeque { + std::mem::take(&mut self.proposals_to_send) + } + + pub fn prepend_batches(&mut self, mut batches: Proposals) { + batches.append(&mut self.proposals_to_send); + self.proposals_to_send = batches; + } + + pub fn get_current_proposal_id(&self) -> Option { + self.current_proposal.as_ref().map(|b| b.id) + } + + pub fn try_finalize_current_batch(&mut self) -> Result<(), Error> { + // TODO handle forced inclusion + self.finalize_current_batch(); + Ok(()) + } + + pub fn remove_current_batch(&mut self) { + self.current_proposal = None; + } + + pub fn finalize_current_batch(&mut self) { + if let Some(batch) = self.current_proposal.take() + && !batch.l2_blocks.is_empty() + { + self.proposals_to_send.push_back(batch); + } + } + + pub fn should_new_block_be_created( + &self, + pending_tx_list: &Option, + current_l2_slot_timestamp: u64, + end_of_sequencing: bool, + ) -> bool { + let number_of_pending_txs = pending_tx_list + .as_ref() + .map(|tx_list| tx_list.tx_list.len()) + .unwrap_or(0) as u64; + + if self.is_empty_block_required(current_l2_slot_timestamp) || end_of_sequencing { + return true; + } + + if number_of_pending_txs >= self.config.preconf_min_txs { + return true; + } + + if let Some(current_proposal) = self.current_proposal.as_ref() + && let Some(last_block) = current_proposal.l2_blocks.last() + { + let number_of_l2_slots = + (current_l2_slot_timestamp.saturating_sub(last_block.timestamp_sec)) * 1000 + / self.slot_clock.get_preconf_heartbeat_ms(); + return number_of_l2_slots > self.config.preconf_max_skipped_l2_slots; + } + + true + } + + pub fn has_current_forced_inclusion(&self) -> bool { + self.current_proposal + .as_ref() + .map(|p| p.num_forced_inclusion > 0) + .unwrap_or(false) + } + + pub fn inc_forced_inclusion(&mut self) -> Result<(), Error> { + self.current_proposal + .as_mut() + .map(|proposal| proposal.num_forced_inclusion += 1) + .ok_or_else(|| anyhow::anyhow!("No current proposal to add forced inclusion to")) + } +} diff --git a/shasta/src/node/proposal_manager/bridge_handler.rs b/shasta/src/node/proposal_manager/bridge_handler.rs new file mode 100644 index 00000000..2e077a44 --- /dev/null +++ b/shasta/src/node/proposal_manager/bridge_handler.rs @@ -0,0 +1,275 @@ +use crate::l2::taiko::Taiko; +use crate::shared_abi::bindings::IBridge::Message; +use crate::{ + l1::execution_layer::{ExecutionLayer, L1BridgeHandlerOps}, + l2::execution_layer::L2BridgeHandlerOps, +}; +use alloy::primitives::{Address, Bytes, FixedBytes}; +use alloy::signers::Signer; +use anyhow::Result; +use common::{l1::ethereum_l1::EthereumL1, utils::cancellation_token::CancellationToken}; +use jsonrpsee::server::{RpcModule, ServerBuilder}; +use serde::{Deserialize, Serialize}; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::{net::SocketAddr, sync::Arc}; +use tokio::sync::mpsc::{self, Receiver}; +use tracing::{error, info, warn}; + +// Sequence of calls on L1: +// 1. User op calldata submission to the submitter +// 2. Proposal with signal slot +// 3. L1Call initiated by an L2 contract + +// Sequence of calls on l2: +// 1. L2Call initiated by an L1 contract + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "status")] +pub enum UserOpStatus { + Pending, + Processing { tx_hash: FixedBytes<32> }, + ProvingBlock { block_id: u64 }, + Rejected { reason: String }, + Executed, +} + +/// Disk-backed user op status store using sled. +#[derive(Clone)] +pub struct UserOpStatusStore { + db: sled::Db, +} + +impl UserOpStatusStore { + pub fn open(path: &str) -> Result { + let db = sled::open(path) + .map_err(|e| anyhow::anyhow!("Failed to open user op status store: {}", e))?; + Ok(Self { db }) + } + + pub fn set(&self, id: u64, status: &UserOpStatus) { + if let Ok(value) = serde_json::to_vec(status) + && let Err(e) = self.db.insert(id.to_be_bytes(), value) + { + error!("Failed to write user op status: {}", e); + } + } + + pub fn get(&self, id: u64) -> Option { + self.db + .get(id.to_be_bytes()) + .ok() + .flatten() + .and_then(|v| serde_json::from_slice(&v).ok()) + } + + pub fn remove(&self, id: u64) { + let _ = self.db.remove(id.to_be_bytes()); + } +} + +#[derive(Debug, Clone, Deserialize)] +pub struct UserOp { + #[serde(default)] + pub id: u64, + pub submitter: Address, + pub calldata: Bytes, +} + +// Data required to build the L1 call transaction initiated by an L2 contract via the bridge +#[derive(Clone, Debug)] +pub struct L1Call { + pub message_from_l2: Message, + // For this POC, this is a signature based proof, but must be a merkle proof in production + pub signal_slot_proof: Bytes, +} + +// Data required to build the L2 call transaction initiated by an L1 contract via the bridge +#[derive(Clone, Debug)] +pub struct L2Call { + pub message_from_l1: Message, + pub signal_slot_on_l2: FixedBytes<32>, +} + +#[derive(Clone)] +struct BridgeRpcContext { + tx: mpsc::Sender, + status_store: UserOpStatusStore, + next_id: Arc, +} + +pub struct BridgeHandler { + ethereum_l1: Arc>, + taiko: Arc, + rx: Receiver, + // Surge: For signing the L1 call signal slot proofs + l1_call_proof_signer: alloy::signers::local::PrivateKeySigner, + status_store: UserOpStatusStore, +} + +impl BridgeHandler { + pub async fn new( + addr: SocketAddr, + ethereum_l1: Arc>, + taiko: Arc, + cancellation_token: CancellationToken, + ) -> Result { + let (tx, rx) = mpsc::channel::(1024); + let status_store = UserOpStatusStore::open("data/user_op_status")?; + + let rpc_context = BridgeRpcContext { + tx, + status_store: status_store.clone(), + next_id: Arc::new(AtomicU64::new(1)), + }; + + let server = ServerBuilder::default() + .build(addr) + .await + .map_err(|e| anyhow::anyhow!("Failed to build RPC server: {}", e))?; + + let mut module = RpcModule::new(rpc_context); + + module.register_async_method("surge_sendUserOp", |params, ctx, _| async move { + let mut user_op: UserOp = params.parse()?; + let id = ctx.next_id.fetch_add(1, Ordering::Relaxed); + user_op.id = id; + + info!( + "Received UserOp: id={}, submitter={:?}, calldata_len={}", + id, + user_op.submitter, + user_op.calldata.len() + ); + + // Set status to Pending + ctx.status_store.set(id, &UserOpStatus::Pending); + + ctx.tx.send(user_op).await.map_err(|e| { + error!("Failed to send UserOp to queue: {}", e); + ctx.status_store.remove(id); + jsonrpsee::types::ErrorObjectOwned::owned( + -32000, + "Failed to queue user operation", + Some(format!("{}", e)), + ) + })?; + + Ok::(id) + })?; + + module.register_async_method("surge_userOpStatus", |params, ctx, _| async move { + let id: u64 = params.one()?; + + match ctx.status_store.get(id) { + Some(status) => Ok::( + serde_json::to_value(status).map_err(|e| { + jsonrpsee::types::ErrorObjectOwned::owned( + -32603, + "Serialization error", + Some(format!("{}", e)), + ) + })?, + ), + None => Err(jsonrpsee::types::ErrorObjectOwned::owned( + -32001, + "UserOp not found", + Some(format!("No user operation with id {}", id)), + )), + } + })?; + + info!("Bridge handler RPC server starting on {}", addr); + let handle = server.start(module); + + tokio::spawn(async move { + cancellation_token.cancelled().await; + info!("Cancellation token triggered, stopping bridge handler RPC server"); + handle.stop().ok(); + }); + + Ok(Self { + ethereum_l1, + taiko, + rx, + // Surge: Hard coding the private key for the POC + // (This is the first private key from foundry anvil) + l1_call_proof_signer: alloy::signers::local::PrivateKeySigner::from_bytes( + &"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + .parse::>()?, + )?, + status_store, + }) + } + + pub fn status_store(&self) -> UserOpStatusStore { + self.status_store.clone() + } + + // Returns any L2 calls initiated by an L1 contract via the Bridge. + // For seamless composability, the users will be submitting a `UserOp` on L1 to interact with + // the Bridge and any other intermediate contract. + pub async fn next_user_op_and_l2_call( + &mut self, + ) -> Result, anyhow::Error> { + if let Ok(user_op) = self.rx.try_recv() { + // This is the message sent from the L1 contract to the L2, and the + // associated signal that is set when the user op is executed + if let Some((message_from_l1, signal_slot_on_l2)) = self + .ethereum_l1 + .execution_layer + .find_message_and_signal_slot(user_op.clone()) + .await? + { + return Ok(Some(( + user_op, + L2Call { + message_from_l1, + signal_slot_on_l2, + }, + ))); + } + + // No L2 call found in the user op - reject it + warn!( + "UserOp id={} rejected: no L2 call found in user op", + user_op.id + ); + self.status_store.set( + user_op.id, + &UserOpStatus::Rejected { + reason: "No L2 call found in user op".to_string(), + }, + ); + } + + Ok(None) + } + + // Surge: Finds L1 calls initiated in a specific L2 block + pub async fn find_l1_call(&mut self, block_id: u64) -> Result, anyhow::Error> { + if let Some((message_from_l2, signal_slot)) = self + .taiko + .l2_execution_layer() + .find_message_and_signal_slot(block_id) + .await? + { + let signature = self.l1_call_proof_signer.sign_hash(&signal_slot).await?; + + let mut signal_slot_proof = [0_u8; 65]; + signal_slot_proof[..32].copy_from_slice(signature.r().to_be_bytes::<32>().as_slice()); + signal_slot_proof[32..64].copy_from_slice(signature.s().to_be_bytes::<32>().as_slice()); + signal_slot_proof[64] = u8::from(signature.v()) + 27; + + return Ok(Some(L1Call { + message_from_l2, + signal_slot_proof: Bytes::from(signal_slot_proof), + })); + } + + Ok(None) + } + + pub fn has_pending_user_ops(&self) -> bool { + !self.rx.is_empty() + } +} diff --git a/shasta/src/node/proposal_manager/mod.rs b/shasta/src/node/proposal_manager/mod.rs index 4de24492..a0f7442b 100644 --- a/shasta/src/node/proposal_manager/mod.rs +++ b/shasta/src/node/proposal_manager/mod.rs @@ -1,17 +1,27 @@ +mod batch_builder; pub mod block_advancer; +pub mod bridge_handler; pub mod l2_block_payload; pub mod proposal; mod proposal_builder; mod proposal_queue; +use crate::l2::bindings::ICheckpointStore::Checkpoint; +use crate::l2::execution_layer::L2BridgeHandlerOps; +use crate::node::proposal_manager::bridge_handler::UserOp; use crate::{ l1::execution_layer::ExecutionLayer, l2::taiko::Taiko, metrics::Metrics, shared::{l2_block_v2::L2BlockV2Draft, l2_tx_lists::PreBuiltTxList}, }; -use alloy::{consensus::BlockHeader, consensus::Transaction}; +use alloy::primitives::FixedBytes; +use alloy::{ + consensus::{BlockHeader, Transaction as TransactionTrait}, + primitives::aliases::U48, +}; use anyhow::Error; +use bridge_handler::BridgeHandler; use common::{batch_builder::BatchBuilderConfig, shared::l2_slot_info_v2::L2SlotContext}; use common::{ l1::{ethereum_l1::EthereumL1, traits::ELTrait}, @@ -20,7 +30,8 @@ use common::{ utils::cancellation_token::CancellationToken, }; use proposal_builder::ProposalBuilder; -use std::sync::Arc; +use std::{net::SocketAddr, sync::Arc}; +use tokio::sync::Mutex; use tracing::{debug, error, info, warn}; use crate::forced_inclusion::ForcedInclusion; @@ -30,6 +41,7 @@ use proposal::Proposals; pub struct ProposalManager { proposal_builder: ProposalBuilder, + bridge_handler: Arc>, ethereum_l1: Arc>, pub taiko: Arc, block_advancer: Arc, @@ -74,12 +86,25 @@ impl ProposalManager { let forced_inclusion = ForcedInclusion::new(ethereum_l1.clone()).await?; + // Initialize bridge handler listening on port 4545 + let bridge_addr: SocketAddr = "127.0.0.1:4545".parse()?; + let bridge_handler = Arc::new(Mutex::new( + BridgeHandler::new( + bridge_addr, + ethereum_l1.clone(), + taiko.clone(), + cancel_token.clone(), + ) + .await?, + )); + Ok(Self { proposal_builder: ProposalBuilder::new( config, ethereum_l1.slot_clock.clone(), metrics.clone(), ), + bridge_handler, ethereum_l1, taiko, block_advancer, @@ -112,6 +137,7 @@ impl ProposalManager { submit_only_full_proposals: bool, l2_slot_timestamp: u64, ) -> Result<(), Error> { + let _status_store = self.bridge_handler.lock().await.status_store(); self.proposal_builder .try_submit_oldest_proposal( self.ethereum_l1.clone(), @@ -193,6 +219,7 @@ impl ProposalManager { let payload = self .proposal_builder .add_fi_block(fi_block, anchor_params)?; + // Surge: Signal slots are not expected in forced inclusions match self .block_advancer .advance_head_to_new_l2_block(payload, l2_slot_context, operation_type) @@ -283,12 +310,76 @@ impl ProposalManager { Ok(preconfed_block) } + // Surge: checks if the bridge handler has pending user ops + pub async fn has_pending_user_ops(&self) -> bool { + return self.bridge_handler.lock().await.has_pending_user_ops(); + } + + // Surge: Adds any pending L2 calls initiated via User Ops on the bridge handler + // to the transaction list in the draft block. + // + // For the POC, only a single L2 call is added per block + // + // Returns the signal slot to be set on L2 + async fn add_pending_l2_call_to_draft_block( + &mut self, + l2_draft_block: &mut L2BlockV2Draft, + ) -> Result)>, anyhow::Error> { + // Check for pending L2 calls from the bridge handler + if let Some((user_op_data, l2_call)) = self + .bridge_handler + .lock() + .await + .next_user_op_and_l2_call() + .await? + { + info!("Processing pending L2 call: {:?}", l2_call); + + // Construct the bridge call transaction via the L2 execution layer + let l2_call_bridge_tx = self + .taiko + .l2_execution_layer() + .construct_l2_call_tx(l2_call.message_from_l1) + .await?; + + info!( + "Inserting L2 call bridge transaction into tx list: {:?}", + l2_call_bridge_tx + ); + + // Insert the bridge transaction into the list + l2_draft_block + .prebuilt_tx_list + .tx_list + .push(l2_call_bridge_tx); + + return Ok(Some((user_op_data, l2_call.signal_slot_on_l2))); + } + + Ok(None) + } + async fn add_draft_block_to_proposal( &mut self, - l2_draft_block: L2BlockV2Draft, + mut l2_draft_block: L2BlockV2Draft, l2_slot_context: &L2SlotContext, operation_type: OperationType, ) -> Result { + let mut anchor_signal_slots: Vec> = vec![]; + + // Surge: Add any pending L2 call from the bridge handler RPC and + // record associated signal slot for the proposal + debug!("Checking for pending L2 calls"); + if let Some((user_op_data, signal_slot)) = self + .add_pending_l2_call_to_draft_block(&mut l2_draft_block) + .await? + { + self.proposal_builder.add_user_op(user_op_data)?; + self.proposal_builder.add_signal_slot(signal_slot)?; + anchor_signal_slots.push(signal_slot); + } else { + debug!("No pending L2 calls"); + } let payload = self.proposal_builder.add_l2_draft_block(l2_draft_block)?; match self @@ -296,7 +387,34 @@ impl ProposalManager { .advance_head_to_new_l2_block(payload, l2_slot_context, operation_type) .await { - Ok(preconfed_block) => Ok(preconfed_block), + Ok(preconfed_block) => { + // Surge: record the state of the preconfed block as a potential checkpoint + self.proposal_builder.set_proposal_checkpoint(Checkpoint { + blockNumber: U48::from(preconfed_block.number), + stateRoot: preconfed_block.state_root, + blockHash: preconfed_block.hash, + })?; + + // Surge: Record any L1 call initiated in the L2 block + // Note: This currently includes ALL L1 calls and not just the ones initiated + // by the user op txn. + // In production, tx hashes of user op calls need to recorded and only L1 calls + // initiated in same transaction need to be considered + debug!("Checking for initiated L1 calls"); + if let Some(l1_call) = self + .bridge_handler + .lock() + .await + .find_l1_call(preconfed_block.number) + .await? + { + self.proposal_builder.add_l1_call(l1_call)?; + } else { + debug!("No L1 calls initiated"); + } + + Ok(preconfed_block) + } Err(err) => { error!("Failed to advance head to new L2 block: {}", err); self.remove_last_l2_block(); @@ -548,6 +666,10 @@ impl ProposalManager { let txs = txs.to_vec(); + // Store block data for checkpoint before block is moved + let block_hash = block.header.hash_slow(); + let block_state_root = block.header.state_root(); + // TODO validate block params self.proposal_builder .recover_from( @@ -560,6 +682,15 @@ impl ProposalManager { is_forced_inclusion, ) .await?; + + // Surge: Set the checkpoint for the recovered block + // This is the L2 block state that should be used as checkpoint for the proposal + self.proposal_builder.set_proposal_checkpoint(Checkpoint { + blockNumber: U48::from(block_height), + stateRoot: block_state_root, + blockHash: block_hash, + })?; + Ok(block.header.timestamp()) } @@ -569,9 +700,6 @@ impl ProposalManager { block_timestamp: u64, parent_timestamp: u64, ) -> Result<(), Error> { - // Validate against derivation rules: - // block.timestamp must be in [lower_bound, proposal_timestamp] - // We use current time as an approximation for proposal_timestamp (upper bound). let now_duration = std::time::SystemTime::now() .duration_since(std::time::SystemTime::UNIX_EPOCH) .map_err(|e| { @@ -595,6 +723,7 @@ impl ProposalManager { pub fn clone_without_proposals(&self, fi_head: u64) -> Self { Self { proposal_builder: self.proposal_builder.clone_without_proposals(), + bridge_handler: self.bridge_handler.clone(), ethereum_l1: self.ethereum_l1.clone(), taiko: self.taiko.clone(), block_advancer: self.block_advancer.clone(), diff --git a/shasta/src/node/proposal_manager/proposal.rs b/shasta/src/node/proposal_manager/proposal.rs index 89c275e7..64e8e4e0 100644 --- a/shasta/src/node/proposal_manager/proposal.rs +++ b/shasta/src/node/proposal_manager/proposal.rs @@ -1,9 +1,12 @@ -use crate::node::proposal_manager::l2_block_payload::L2BlockV2Payload; -use alloy::primitives::{Address, B256}; +use crate::l2::bindings::ICheckpointStore::Checkpoint; +use crate::node::proposal_manager::{ + bridge_handler::{L1Call, UserOp}, + l2_block_payload::L2BlockV2Payload, +}; +use alloy::primitives::{Address, B256, FixedBytes}; use common::shared::l2_block_v2::{L2BlockV2, L2BlockV2Draft}; use std::collections::VecDeque; use std::time::Instant; -use taiko_bindings::anchor::ICheckpointStore::Checkpoint; use taiko_protocol::shasta::manifest::{BlockManifest, DerivationSourceManifest}; use tracing::{debug, warn}; @@ -24,6 +27,15 @@ pub struct Proposal { /// Set to true when this proposal has been dispatched to the transaction monitor /// and is awaiting on-chain confirmation. pub pending_confirmation: bool, + // Surge: the state sync checkpoint that is signed and sent as a proof + // along with the proposal to Surge inbox + pub checkpoint: Checkpoint, + // Surge: User ops that initiate L2 calls + pub user_ops: Vec, + // Surge: Signal slots to set via anchor with the proposal + pub signal_slots: Vec>, + // Surge: L1 calls initiated by any L2 contracts + pub l1_calls: Vec, } impl Proposal { @@ -214,6 +226,10 @@ mod test { num_forced_inclusion: 0, created_at_sec: 0, pending_confirmation: false, + checkpoint: Checkpoint::default(), + user_ops: vec![], + signal_slots: vec![], + l1_calls: vec![], }; proposal.compress(); diff --git a/shasta/src/node/proposal_manager/proposal_builder.rs b/shasta/src/node/proposal_manager/proposal_builder.rs index e2fef8d9..e253240d 100644 --- a/shasta/src/node/proposal_manager/proposal_builder.rs +++ b/shasta/src/node/proposal_manager/proposal_builder.rs @@ -2,13 +2,17 @@ use std::{collections::VecDeque, sync::Arc}; use super::proposal::Proposals; use super::proposal_queue::ProposalQueue; +use crate::l2::bindings::ICheckpointStore::Checkpoint; +use crate::node::proposal_manager::bridge_handler::{L1Call, UserOp}; use crate::node::proposal_manager::l2_block_payload::L2BlockV2Payload; use crate::{ l1::execution_layer::ExecutionLayer, metrics::Metrics, - node::proposal_manager::proposal::Proposal, shared::l2_tx_lists::PreBuiltTxList, + node::proposal_manager::proposal::Proposal, }; use alloy::primitives::Address; +use alloy::primitives::FixedBytes; use anyhow::Error; +use common::shared::l2_tx_lists::PreBuiltTxList; use common::{ batch_builder::BatchBuilderConfig, shared::l2_block_v2::{L2BlockV2, L2BlockV2Draft}, @@ -17,8 +21,7 @@ use common::{ l1::{ethereum_l1::EthereumL1, slot_clock::SlotClock}, shared::anchor_block_info::AnchorBlockInfo, }; -use taiko_bindings::anchor::ICheckpointStore::Checkpoint; -use tracing::{debug, trace, warn}; +use tracing::{debug, info, trace, warn}; pub struct ProposalBuilder { config: BatchBuilderConfig, @@ -108,6 +111,10 @@ impl ProposalBuilder { num_forced_inclusion: 0, created_at_sec: timestamp, pending_confirmation: false, + checkpoint: Checkpoint::default(), + user_ops: vec![], + signal_slots: vec![], + l1_calls: vec![], }); } @@ -217,6 +224,11 @@ impl ProposalBuilder { num_forced_inclusion: 0, created_at_sec: l2_block_timestamp_sec, pending_confirmation: false, + checkpoint: Checkpoint::default(), + // Surge: This is NOT OK for recovery, but fine for the POC. + user_ops: vec![], + signal_slots: vec![], + l1_calls: vec![], }); } @@ -239,7 +251,7 @@ impl ProposalBuilder { proposal.anchor_state_root = anchor_info.state_root(); } - let bytes_length = crate::shared::l2_tx_lists::rlp_encode(&tx_list).len() as u64; + let bytes_length = common::shared::l2_tx_lists::rlp_encode(&tx_list).len() as u64; let l2_draft_block = L2BlockV2Draft { prebuilt_tx_list: PreBuiltTxList { @@ -260,7 +272,7 @@ impl ProposalBuilder { } let l2_block = L2BlockV2::new_from( - crate::shared::l2_tx_lists::PreBuiltTxList { + common::shared::l2_tx_lists::PreBuiltTxList { tx_list, estimated_gas_used: 0, bytes_length, @@ -350,11 +362,10 @@ impl ProposalBuilder { "Submitting proposal" ); - // Dispatches tx building + monitoring to a background task (returns immediately). - // Build errors (EstimationFailed, etc.) are reported via error_notification_channel. + // Send the full proposal (with user ops, signal slots, L1 calls) to L1 ethereum_l1 .execution_layer - .send_proposal_to_l1(proposal.l2_blocks.clone(), proposal.num_forced_inclusion) + .send_proposal_to_l1(proposal.clone(), None, None) .await?; // Mark the proposal as dispatched — it will be removed once the monitor confirms. @@ -496,6 +507,50 @@ impl ProposalBuilder { .as_ref() .is_some_and(|p| p.has_forced_inclusion()) } + + // Surge: adds user ops that initiate L2 calls + pub fn add_user_op(&mut self, user_op_data: UserOp) -> Result<&Proposal, Error> { + if let Some(current_proposal) = self.current_proposal.as_mut() { + current_proposal.user_ops.push(user_op_data.clone()); + info!("Added user op: {:?}", user_op_data); + Ok(current_proposal) + } else { + Err(anyhow::anyhow!("No current proposal")) + } + } + + // Surge: adds signal slots to make same slot L2 call valid + pub fn add_signal_slot(&mut self, signal_slot: FixedBytes<32>) -> Result<&Proposal, Error> { + if let Some(current_proposal) = self.current_proposal.as_mut() { + current_proposal.signal_slots.push(signal_slot); + info!("Added signal slot: {:?}", signal_slot); + Ok(current_proposal) + } else { + Err(anyhow::anyhow!("No current proposal")) + } + } + + // Surge: adds L1 calls initiated by L2 contracts + pub fn add_l1_call(&mut self, l1_call: L1Call) -> Result<&Proposal, Error> { + if let Some(current_proposal) = self.current_proposal.as_mut() { + current_proposal.l1_calls.push(l1_call.clone()); + info!("Added L1 call: {:?}", l1_call); + Ok(current_proposal) + } else { + Err(anyhow::anyhow!("No current proposal")) + } + } + + // Surge: Sets the proposal checkpoint + pub fn set_proposal_checkpoint(&mut self, checkpoint: Checkpoint) -> Result<&Proposal, Error> { + if let Some(current_proposal) = self.current_proposal.as_mut() { + current_proposal.checkpoint = checkpoint.clone(); + debug!("Update proposal checkpoint: {:?}", checkpoint); + Ok(current_proposal) + } else { + Err(anyhow::anyhow!("No current proposal")) + } + } } #[cfg(test)] diff --git a/shasta/src/shared_abi/Bridge.json b/shasta/src/shared_abi/Bridge.json new file mode 100644 index 00000000..8f768573 --- /dev/null +++ b/shasta/src/shared_abi/Bridge.json @@ -0,0 +1,738 @@ +{ + "abi": [ + { + "type": "function", + "name": "context", + "inputs": [], + "outputs": [ + { + "name": "ctx_", + "type": "tuple", + "internalType": "struct IBridge.Context", + "components": [ + { + "name": "msgHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "srcChainId", + "type": "uint64", + "internalType": "uint64" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "failMessage", + "inputs": [ + { + "name": "_message", + "type": "tuple", + "internalType": "struct IBridge.Message", + "components": [ + { + "name": "id", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "fee", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "gasLimit", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "srcChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "srcOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "destChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "destOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "hashMessage", + "inputs": [ + { + "name": "_message", + "type": "tuple", + "internalType": "struct IBridge.Message", + "components": [ + { + "name": "id", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "fee", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "gasLimit", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "srcChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "srcOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "destChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "destOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "isMessageSent", + "inputs": [ + { + "name": "_message", + "type": "tuple", + "internalType": "struct IBridge.Message", + "components": [ + { + "name": "id", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "fee", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "gasLimit", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "srcChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "srcOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "destChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "destOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "nextMessageId", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint64", + "internalType": "uint64" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "processMessage", + "inputs": [ + { + "name": "_message", + "type": "tuple", + "internalType": "struct IBridge.Message", + "components": [ + { + "name": "id", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "fee", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "gasLimit", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "srcChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "srcOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "destChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "destOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "_proof", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "uint8", + "internalType": "enum IBridge.Status" + }, + { + "name": "", + "type": "uint8", + "internalType": "enum IBridge.StatusReason" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "recallMessage", + "inputs": [ + { + "name": "_message", + "type": "tuple", + "internalType": "struct IBridge.Message", + "components": [ + { + "name": "id", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "fee", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "gasLimit", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "srcChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "srcOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "destChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "destOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "_proof", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "retryMessage", + "inputs": [ + { + "name": "_message", + "type": "tuple", + "internalType": "struct IBridge.Message", + "components": [ + { + "name": "id", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "fee", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "gasLimit", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "srcChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "srcOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "destChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "destOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "_isLastAttempt", + "type": "bool", + "internalType": "bool" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "sendMessage", + "inputs": [ + { + "name": "_message", + "type": "tuple", + "internalType": "struct IBridge.Message", + "components": [ + { + "name": "id", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "fee", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "gasLimit", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "srcChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "srcOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "destChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "destOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [ + { + "name": "msgHash_", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "message_", + "type": "tuple", + "internalType": "struct IBridge.Message", + "components": [ + { + "name": "id", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "fee", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "gasLimit", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "srcChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "srcOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "destChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "destOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "stateMutability": "payable" + }, + { + "type": "event", + "name": "MessageSent", + "inputs": [ + { + "name": "msgHash", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "message", + "type": "tuple", + "indexed": false, + "internalType": "struct IBridge.Message", + "components": [ + { + "name": "id", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "fee", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "gasLimit", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "srcChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "srcOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "destChainId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "destOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "MessageStatusChanged", + "inputs": [ + { + "name": "msgHash", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "status", + "type": "uint8", + "indexed": false, + "internalType": "enum IBridge.Status" + } + ], + "anonymous": false + } + ] +} \ No newline at end of file diff --git a/shasta/src/shared_abi/bindings.rs b/shasta/src/shared_abi/bindings.rs new file mode 100644 index 00000000..2c6a471e --- /dev/null +++ b/shasta/src/shared_abi/bindings.rs @@ -0,0 +1,17 @@ +#![allow(clippy::too_many_arguments)] + +use alloy::sol; + +sol!( + #[allow(missing_docs)] + #[sol(rpc)] + #[derive(Debug)] + Bridge, + "src/shared_abi/Bridge.json" +); + +// SignalSent event emitted by the SignalService contract +sol! { + #[allow(missing_docs)] + event SignalSent(address app, bytes32 signal, bytes32 slot, bytes32 value); +} diff --git a/shasta/src/shared_abi/mod.rs b/shasta/src/shared_abi/mod.rs new file mode 100644 index 00000000..90c70dcc --- /dev/null +++ b/shasta/src/shared_abi/mod.rs @@ -0,0 +1 @@ +pub mod bindings;